diff --git a/docs/G_Digraph_2_Intro.fsx b/docs/G_Digraph_2_Intro.fsx index 410c0d9..ec2730b 100644 --- a/docs/G_Digraph_2_Intro.fsx +++ b/docs/G_Digraph_2_Intro.fsx @@ -110,7 +110,7 @@ let karateOutput = sprintf "Successfully imported the undirected karate graph! I (** A conversion into an Adjacency Matrix is also very easily achievable. It can be executed as follows. *) -let monkeyAdjacencyMatrix = DiGraph.toMatrix monkeyGraph +let monkeyAdjacencyMatrix = DiGraph.toAdjacencyMatrix id monkeyGraph (***include-value: monkeyAdjacencyMatrix***) (** diff --git a/docs/M_4_CentralityMeasures.fsx b/docs/M_4_CentralityMeasures.fsx index 8647136..3788819 100644 --- a/docs/M_4_CentralityMeasures.fsx +++ b/docs/M_4_CentralityMeasures.fsx @@ -108,8 +108,8 @@ It quantifies how far a node is from the farthest other node in the network in t In other words, it represents the maximum distance between a node and any other node in the graph. *) -let eccentricity (node:'NodeKey) = - Measures.Eccentricity.computeOfNodeWithEdgeData centralityGraph node +let eccentricity (node:int) = + Measures.Eccentricity.computeOfNodeWithEdgeData(centralityGraph,node) renderCyGraph (fun x -> CyParam.label ($"Node: {x};Eccentricity: {eccentricity x}")) (*** include-it-raw ***) diff --git a/src/Graphoscope/AdjGraph.fs b/src/Graphoscope/AdjGraph.fs index 6df3fb5..7bb534b 100644 --- a/src/Graphoscope/AdjGraph.fs +++ b/src/Graphoscope/AdjGraph.fs @@ -469,6 +469,226 @@ type AdjGraph() = AdjGraph.getSubGraphOfNodeSeq graph subNodes + + ///Returns if the given graph features loops + static member hasLoops (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) = + graph + |> Seq.countIf (fun (kv) -> + let d,n = kv.Value + n|>Dictionary.containsKey kv.Key + ) + |> fun x -> x<>0 + + ///Returns if the given graph does not feature loops + static member isSimple (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) = + graph + |> Seq.countIf (fun (kv) -> + let d,n = kv.Value + n|>Dictionary.containsKey kv.Key + ) + |> fun x -> x=0 + + static member getNodeLabel (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (nk:'NodeKey) :'NodeData = + graph.Item nk + |> fun (d,n) -> d + + + static member filterGraphByNodeKey (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (nodeFilter:'NodeKey -> bool) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + let newGraph :AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + if nodeFilter (kvp.Key) then + let d,n = kvp.Value + + let nNew:Dictionary<'NodeKey,'EdgeData> = + let dicP :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + n|>Seq.iter(fun tsv -> if nodeFilter tsv.Key then dicP.Add(tsv.Key,tsv.Value)) + dicP + + newGraph.Add (kvp.Key,(d,nNew)) + ) + newGraph + + static member filterGraphByNodeKeyInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (nodeFilter:'NodeKey -> bool) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp) -> + if nodeFilter (kvp.Key) |> not then + graph.Remove kvp.Key |> ignore + else + let d,n = kvp.Value + n|>Seq.iter(fun tsv -> if nodeFilter tsv.Key |>not then n.Remove tsv.Key|>ignore) + ) + graph + + static member filterGraphByEdge (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (edgeFilter:'NodeKey -> 'NodeKey -> bool) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + let newGraph :AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = Dictionary<_,_>() + + graph + |> Seq.iter(fun (kvp) -> + let (d,n) = kvp.Value + + let nNew:Dictionary<'NodeKey,'EdgeData> = + let dicS :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + n|>Seq.iter(fun tvp -> if edgeFilter kvp.Key tvp.Key then dicS.Add(tvp.Key,tvp.Value)) + dicS + newGraph.Add(kvp.Key,(d,nNew)) + ) + + newGraph + + static member filterGraphByEdgeInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (edgeFilter:'NodeKey -> 'NodeKey -> bool) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp) -> + let (d,n) = kvp.Value + (n|>Seq.iter(fun tvp -> if edgeFilter kvp.Key tvp.Key |>not then n.Remove tvp.Key|>ignore)) + ) + + graph + + static member filterGraphByContext (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (contextFilter: 'context -> bool) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + let newGraph :AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + if contextFilter (kvp.Value) then + let d,n = kvp.Value + + let nNew:Dictionary<'NodeKey,'EdgeData> = + let dicP :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + n|>Seq.iter(fun tsv -> if contextFilter graph.[tsv.Key] then dicP.Add(tsv.Key,tsv.Value)) + dicP + + newGraph.Add (kvp.Key,(d,nNew)) + ) + newGraph + + static member filterGraphByContextInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>)(contextFilter: 'context -> bool) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp) -> + if contextFilter (kvp.Value) |> not then + graph.Remove kvp.Key |> ignore + else + let d,n = kvp.Value + n|>Seq.iter(fun tsv -> if contextFilter graph.[tsv.Key] |>not then n.Remove tsv.Key|>ignore) + ) + graph + + static member mapNodeData (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeData -> 'NodeData_) : AdjGraph<'NodeKey, 'NodeData_, 'EdgeData> = + let newGraph :AdjGraph<'NodeKey, 'NodeData_, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + + let d,n = kvp.Value + let mappedNodeData = mapping d + let nCloned = new Dictionary<'NodeKey,'EdgeData>(n) + + newGraph.Add (kvp.Key,(mappedNodeData,nCloned)) + ) + newGraph + + static member mapNodeDataInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeData -> 'NodeData) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp) -> + let d,n = kvp.Value + let mappedNodeData = mapping d + graph.Item kvp.Key <- (mappedNodeData,n) + ) + graph + + static member mapNodeKey (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey_) : AdjGraph<'NodeKey_, 'NodeData, 'EdgeData> = + let newGraph :AdjGraph<'NodeKey_, 'NodeData, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + let newKey = mapping kvp.Key + let d,n = kvp.Value + + let nNew:Dictionary<'NodeKey_,'EdgeData> = + let dicS :Dictionary<'NodeKey_,'EdgeData> = new Dictionary<_,_>() + n + |>Seq.iter(fun tvp -> + let mappedNode = mapping tvp.Key + dicS.Add(mappedNode,tvp.Value) + ) + dicS + + newGraph.Add (newKey,(d,nNew)) + ) + newGraph + + static member mapNodeKeyInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp) -> + let newKey = mapping kvp.Key + let d,n = kvp.Value + + let nNew:Dictionary<'NodeKey,'EdgeData> = + let dicS :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + n + |>Seq.iter(fun tvp -> + let mappedNode = mapping tvp.Key + dicS.Add(mappedNode,tvp.Value) + ) + dicS + + graph.Add (newKey,(d,nNew)) + graph.Remove kvp.Key |> ignore + ) + graph + + static member mapEdgeData (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey -> 'EdgeData -> 'EdgeData_) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData_> = + let newGraph :AdjGraph<'NodeKey, 'NodeData, 'EdgeData_> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + let (d,n) = kvp.Value + + let nNew:Dictionary<'NodeKey,'EdgeData_> = + let dicS :Dictionary<'NodeKey,'EdgeData_> = new Dictionary<_,_>() + n + |>Seq.iter(fun tvp -> + let mappedEdgeData = mapping kvp.Key tvp.Key tvp.Value + dicS.Add(tvp.Key,mappedEdgeData) + ) + dicS + + newGraph.Add(kvp.Key,(d,nNew)) + ) + + newGraph + + static member mapEdgeDataInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey -> 'EdgeData -> 'EdgeData) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun kvp -> + let (d,n) = kvp.Value + + n + |>Seq.iter(fun (tvp:KeyValuePair<'NodeKey,'EdgeData>) -> + let newEdgeData :'EdgeData = mapping (kvp.Key) (tvp.Key) (tvp.Value) + n.Item tvp.Key <- (newEdgeData) + ) + ) + graph + + static member mapContext (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (contextMapping:'context -> 'context_) : AdjGraph<'NodeKey, 'NodeData_, 'EdgeData_> = + let newGraph :AdjGraph<'NodeKey, 'NodeData_, 'EdgeData_> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + let (d,n) = kvp.Value + let mappedContext = contextMapping kvp.Value + + newGraph.Add(kvp.Key,(mappedContext)) + ) + + newGraph + + static member mapContextInplace (graph: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>)(contextMapping:'context -> 'context) : AdjGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun kvp -> + let (d,n) = kvp.Value + let mappedContext = contextMapping kvp.Value + graph.Item kvp.Key <- mappedContext + ) + graph + + module AdjGraph = /// diff --git a/src/Graphoscope/FGraph.fs b/src/Graphoscope/FGraph.fs index 5bed1b6..33dd0c5 100644 --- a/src/Graphoscope/FGraph.fs +++ b/src/Graphoscope/FGraph.fs @@ -104,7 +104,6 @@ type FGraph() = /// /// Adds a labeled, directed edge to the graph. /// - /// FGraph with new element static member addElement (nk1 : 'NodeKey) (nd1 : 'NodeData) (nk2 : 'NodeKey) (nd2 : 'NodeData) (ed : 'EdgeData) (g : FGraph<'NodeKey,'NodeData,'EdgeData>) : FGraph<'NodeKey,'NodeData,'EdgeData> = let mutable contextNk1 = (null,Unchecked.defaultof<'NodeData>,null) @@ -368,7 +367,7 @@ type FGraph() = p1.Add(nk1,ed) g - + ///Add labeled, directed edges to the graph. static member addEdges (edgeSeq:seq<('NodeKey)*('NodeKey)*('EdgeData)>) (g : FGraph<'NodeKey,'NodeData,'EdgeData>) : FGraph<'NodeKey,'NodeData,'EdgeData> = Seq.iter (fun (nk1,nk2,ed) -> FGraph.addEdge nk1 nk2 ed g|>ignore) edgeSeq|>ignore @@ -395,7 +394,19 @@ type FGraph() = let (_, _, s) = skv.Value for tkv in s do action skv.Key tkv.Key tkv.Value - + + /// Maps the given function on each egdge of the graph + static member mapEdges (action : 'NodeKey -> 'NodeKey -> 'EdgeData -> 'U) (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = + graph + |>Seq.map(fun skv -> + skv.Value + |> fun (_,_,s) -> s + |> Seq.map(fun tkv -> + action skv.Key tkv.Key tkv.Value + ) + ) + |> Seq.concat + /// Applies the given function on every edge of the graph, which also receives an ascending integer index. static member iteriEdges (action : int -> 'NodeKey -> 'NodeKey -> 'EdgeData -> unit) (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = let mutable index = -1 @@ -405,6 +416,53 @@ type FGraph() = index <- index + 1 action index skv.Key tkv.Key tkv.Value + /// Maps the given function on each egdge of the graph + static member mapiEdges (action : int -> 'NodeKey -> 'NodeKey -> 'EdgeData -> 'U) (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = + let mutable index = -1 + graph + |>Seq.map(fun skv -> + skv.Value + |> fun (_,_,s) -> s + |> Seq.map(fun tkv -> + index <- index + 1 + action index skv.Key tkv.Key tkv.Value + ) + ) + |> Seq.concat + + ///Set the EdgeData of a given Edge between nk1 and nk2 to the given EdgeData + static member setEdgeData (nk1: 'NodeKey) (nk2: 'NodeKey)(ed: 'EdgeData) (g : FGraph<'NodeKey, 'NodeData, 'EdgeData>) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + let mutable contextNk1 = (null,Unchecked.defaultof<'NodeData>,null) + match g.TryGetValue(nk1,&contextNk1) with + | false -> failwithf "Source Node %O does not exist" nk1 + | true -> + if nk1 <> nk2 then + let mutable contextNk2 = (null,Unchecked.defaultof<'NodeData>,null) + match g.TryGetValue(nk2,&contextNk2) with + | false -> failwithf "Edge to Target Node %O does not exist" nk2 + | true -> + let p1, nd1, s1 = contextNk1 + //let mutable s1_ = Unchecked.defaultof<'EdgeData> + match s1.ContainsKey(nk2) with + | true -> + s1.Item nk2 <- (ed) + let (p2, nd2, s2) = contextNk2 + p2.Item nk1 <- (ed) + + | false -> + failwithf "An Edge between Source Node %O Target Node %O does not exist" nk1 nk2 + + else + let p1, nd1, s1 = contextNk1 + match s1.ContainsKey(nk1) with + | true -> + s1.Item nk2 <- (ed) + p1.Item nk1 <- (ed) + | false -> + failwithf "An Edge between Source Node %O Target Node %O does not exist" nk1 nk2 + + g + /// /// Returns the FGraph edges as a sequence of edges /// @@ -448,7 +506,282 @@ type FGraph() = // static member filter (predicate) // ///Removes an edge from the graph. // static member remove - + + ///Returns if the given graph features loops + static member hasLoops (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = + graph + |> Seq.countIf (fun (kv: KeyValuePair<'NodeKey,FContext<'NodeKey,'NodeData,'EdgeData>>) -> + let p,d,s = kv.Value + p|>Dictionary.containsKey kv.Key + ) + |> fun x -> x<>0 + + ///Returns if the given graph does not feature loops + static member isSimple (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = + graph + |> Seq.countIf (fun (kv: KeyValuePair<'NodeKey,FContext<'NodeKey,'NodeData,'EdgeData>>) -> + let p,d,s = kv.Value + p|>Dictionary.containsKey kv.Key + ) + |> fun x -> x=0 + + static member getNodeLabel (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (nk:'NodeKey) :'NodeData = + graph.Item nk + |> fun (p,d,s) -> d + + + //TODO + static member getSubgraphOfNodeSeq (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (nkSeq:seq<'NodeKey>) = + let nkSet = Set.ofSeq nkSeq + graph + |> FGraph.toSeq + |> Seq.filter(fun (s,sd,t,td,w) -> + nkSet.Contains s && nkSet.Contains t) + |> FGraph.ofSeq + + static member filterGraphByNodeKey (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (nodeFilter:'NodeKey -> bool) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + let newGraph :FGraph<'NodeKey, 'NodeData, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + if nodeFilter (kvp.Key) then + let p,d,s = kvp.Value + + let sNew:Dictionary<'NodeKey,'EdgeData> = + let dicS :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + s|>Seq.iter(fun tsv -> if nodeFilter tsv.Key then dicS.Add(tsv.Key,tsv.Value)) + dicS + let pNew:Dictionary<'NodeKey,'EdgeData> = + let dicP :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + p|>Seq.iter(fun tsv -> if nodeFilter tsv.Key then dicP.Add(tsv.Key,tsv.Value)) + dicP + + newGraph.Add (kvp.Key,(pNew,d,sNew)) + ) + newGraph + + static member filterGraphByNodeKeyInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (nodeFilter:'NodeKey -> bool) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp:KeyValuePair<'NodeKey,FContext<'NodeKey, 'NodeData, 'EdgeData>>) -> + if nodeFilter (kvp.Key) |> not then + graph.Remove kvp.Key |> ignore + else + let p,d,s = kvp.Value + p|>Seq.iter(fun tsv -> if nodeFilter tsv.Key |>not then p.Remove tsv.Key|>ignore) + s|>Seq.iter(fun tsv -> if nodeFilter tsv.Key |>not then s.Remove tsv.Key|>ignore) + ) + graph + + static member filterGraphByEdge (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (edgeFilter:'NodeKey -> 'NodeKey -> bool) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + let newGraph :FGraph<'NodeKey, 'NodeData, 'EdgeData> = Dictionary<_,_>() + + graph + |> Seq.iter(fun (kvp) -> + let (p,d,s) = kvp.Value + let pNew:Dictionary<'NodeKey,'EdgeData> = + let dicP :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + p|>Seq.iter(fun tvp -> if edgeFilter tvp.Key kvp.Key then dicP.Add(tvp.Key,tvp.Value)) + dicP + let sNew:Dictionary<'NodeKey,'EdgeData> = + let dicS :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + s|>Seq.iter(fun tvp -> if edgeFilter kvp.Key tvp.Key then dicS.Add(tvp.Key,tvp.Value)) + dicS + newGraph.Add(kvp.Key,(pNew,d,sNew)) + ) + + newGraph + + static member filterGraphByEdgeInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (edgeFilter:'NodeKey -> 'NodeKey -> bool) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp) -> + let (p,d,s) = kvp.Value + (p|>Seq.iter(fun tvp -> if edgeFilter tvp.Key kvp.Key |>not then p.Remove tvp.Key|>ignore)) + (s|>Seq.iter(fun tvp -> if edgeFilter kvp.Key tvp.Key |>not then s.Remove tvp.Key|>ignore)) + ) + + graph + + static member filterGraphByContext (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (contextFilter:FContext<'NodeKey, 'NodeData, 'EdgeData> -> bool) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + let newGraph :FGraph<'NodeKey, 'NodeData, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + if contextFilter (kvp.Value) then + let p,d,s = kvp.Value + + let sNew:Dictionary<'NodeKey,'EdgeData> = + let dicS :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + s|>Seq.iter(fun tsv -> if contextFilter graph.[tsv.Key] then dicS.Add(tsv.Key,tsv.Value)) + dicS + let pNew:Dictionary<'NodeKey,'EdgeData> = + let dicP :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + p|>Seq.iter(fun tsv -> if contextFilter graph.[tsv.Key] then dicP.Add(tsv.Key,tsv.Value)) + dicP + + newGraph.Add (kvp.Key,(pNew,d,sNew)) + ) + newGraph + + static member filterGraphByContextInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (contextFilter:FContext<'NodeKey, 'NodeData, 'EdgeData> -> bool) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp:KeyValuePair<'NodeKey,FContext<'NodeKey, 'NodeData, 'EdgeData>>) -> + if contextFilter (kvp.Value) |> not then + graph.Remove kvp.Key |> ignore + else + let p,d,s = kvp.Value + p|>Seq.iter(fun tsv -> if contextFilter graph.[tsv.Key] |>not then p.Remove tsv.Key|>ignore) + s|>Seq.iter(fun tsv -> if contextFilter graph.[tsv.Key] |>not then s.Remove tsv.Key|>ignore) + ) + graph + + static member mapNodeData (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeData -> 'NodeData_) : FGraph<'NodeKey, 'NodeData_, 'EdgeData> = + let newGraph :FGraph<'NodeKey, 'NodeData_, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + + let p,d,s = kvp.Value + let mappedNodeData = mapping d + let pCloned = new Dictionary<'NodeKey,'EdgeData>(p) + let sCloned = new Dictionary<'NodeKey,'EdgeData>(s) + + newGraph.Add (kvp.Key,(pCloned,mappedNodeData,sCloned)) + ) + newGraph + + static member mapNodeDataInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeData -> 'NodeData) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp:KeyValuePair<'NodeKey,FContext<'NodeKey, 'NodeData, 'EdgeData>>) -> + let p,d,s = kvp.Value + let mappedNodeData = mapping d + graph.Item kvp.Key <- (p,mappedNodeData,s) + ) + graph + + static member mapNodeKey (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey_) : FGraph<'NodeKey_, 'NodeData, 'EdgeData> = + let newGraph :FGraph<'NodeKey_, 'NodeData, 'EdgeData> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + let newKey = mapping kvp.Key + let p,d,s = kvp.Value + + let pNew:Dictionary<'NodeKey_,'EdgeData> = + let dicP :Dictionary<'NodeKey_,'EdgeData> = new Dictionary<_,_>() + p + |>Seq.iter(fun tvp -> + let mappedNode = mapping tvp.Key + dicP.Add(mappedNode,tvp.Value) + ) + dicP + + let sNew:Dictionary<'NodeKey_,'EdgeData> = + let dicS :Dictionary<'NodeKey_,'EdgeData> = new Dictionary<_,_>() + s + |>Seq.iter(fun tvp -> + let mappedNode = mapping tvp.Key + dicS.Add(mappedNode,tvp.Value) + ) + dicS + + newGraph.Add (newKey,(pNew,d,sNew)) + ) + newGraph + + static member mapNodeKeyInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun (kvp:KeyValuePair<'NodeKey,FContext<'NodeKey, 'NodeData, 'EdgeData>>) -> + let newKey = mapping kvp.Key + let p,d,s = kvp.Value + + let pNew:Dictionary<'NodeKey,'EdgeData> = + let dicP :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + p + |>Seq.iter(fun tvp -> + let mappedNode = mapping tvp.Key + dicP.Add(mappedNode,tvp.Value) + ) + dicP + + let sNew:Dictionary<'NodeKey,'EdgeData> = + let dicS :Dictionary<'NodeKey,'EdgeData> = new Dictionary<_,_>() + s + |>Seq.iter(fun tvp -> + let mappedNode = mapping tvp.Key + dicS.Add(mappedNode,tvp.Value) + ) + dicS + + graph.Add (newKey,(pNew,d,sNew)) + graph.Remove kvp.Key |> ignore + ) + graph + + static member mapEdgeData (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey -> 'EdgeData -> 'EdgeData_) : FGraph<'NodeKey, 'NodeData, 'EdgeData_> = + let newGraph :FGraph<'NodeKey, 'NodeData, 'EdgeData_> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + let (p,d,s) = kvp.Value + + let pNew:Dictionary<'NodeKey,'EdgeData_> = + let dicP :Dictionary<'NodeKey,'EdgeData_> = new Dictionary<_,_>() + p + |>Seq.iter(fun tvp -> + let mappedEdgeData = mapping tvp.Key kvp.Key tvp.Value + dicP.Add(tvp.Key,mappedEdgeData) + ) + dicP + + let sNew:Dictionary<'NodeKey,'EdgeData_> = + let dicS :Dictionary<'NodeKey,'EdgeData_> = new Dictionary<_,_>() + s + |>Seq.iter(fun tvp -> + let mappedEdgeData = mapping kvp.Key tvp.Key tvp.Value + dicS.Add(tvp.Key,mappedEdgeData) + ) + dicS + + newGraph.Add(kvp.Key,(pNew,d,sNew)) + ) + + newGraph + + static member mapEdgeDataInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (mapping:'NodeKey -> 'NodeKey -> 'EdgeData -> 'EdgeData) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun kvp -> + let (p,d,s) = kvp.Value + + p + |>Seq.iter(fun (tvp:KeyValuePair<'NodeKey,'EdgeData>) -> + let newEdgeData :'EdgeData = mapping (tvp.Key) (kvp.Key) (tvp.Value) + p.Item tvp.Key <- (newEdgeData) + ) + + s + |>Seq.iter(fun (tvp:KeyValuePair<'NodeKey,'EdgeData>) -> + let newEdgeData :'EdgeData = mapping (kvp.Key) (tvp.Key) (tvp.Value) + s.Item tvp.Key <- (newEdgeData) + ) + ) + graph + + static member mapContext (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (contextMapping:FContext<'NodeKey, 'NodeData, 'EdgeData> -> FContext<'NodeKey, 'NodeData_, 'EdgeData_>) : FGraph<'NodeKey, 'NodeData_, 'EdgeData_> = + let newGraph :FGraph<'NodeKey, 'NodeData_, 'EdgeData_> = Dictionary<_,_>() + graph + |> Seq.iter(fun kvp -> + let (p,d,s) = kvp.Value + let mappedContext = contextMapping kvp.Value + + newGraph.Add(kvp.Key,(mappedContext)) + ) + + newGraph + + static member mapContextInplace (graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) (contextMapping:FContext<'NodeKey, 'NodeData, 'EdgeData> -> FContext<'NodeKey, 'NodeData, 'EdgeData>) : FGraph<'NodeKey, 'NodeData, 'EdgeData> = + graph + |> Seq.iter(fun kvp -> + let (p,d,s) = kvp.Value + let mappedContext = contextMapping kvp.Value + graph.Item kvp.Key <- mappedContext + ) + graph + module FGraph = diff --git a/src/Graphoscope/Graphoscope.fsproj b/src/Graphoscope/Graphoscope.fsproj index ebd9fc4..794de9d 100644 --- a/src/Graphoscope/Graphoscope.fsproj +++ b/src/Graphoscope/Graphoscope.fsproj @@ -44,6 +44,7 @@ + diff --git a/src/Graphoscope/Measures/LongestPath.fs b/src/Graphoscope/Measures/LongestPath.fs new file mode 100644 index 0000000..d0da2a4 --- /dev/null +++ b/src/Graphoscope/Measures/LongestPath.fs @@ -0,0 +1,189 @@ +namespace Graphoscope.Measures + +open Graphoscope +open System.Collections.Generic + +type LongestPath() = + + /// + /// 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. + /// + /// The node from which to check for the existence of a cycle. + /// The FGraph in which to search for the cycle. + /// True if a cycle is found, otherwise false. + 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 + + /// + /// Determines whether a cycle exists in the given FGraph. + /// + /// The FGraph to check for the presence of a cycle + /// True if a cycle is found, otherwise false. + 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 + + /// + /// Computes longest paths from for using an inverse Dijkstra's algorithm. + /// + /// Calculate the longest paths from this node. + /// Function to convert the EdgeData to a float. + /// The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph. + /// If there isn't a path between two edges, the distance is set to `infinity`. + /// Tuples of an ordered list containing the path and the distance of the longest path. + 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.)) + + /// + /// Computes longest paths from for using an inverse Dijkstra's algorithm. + /// + /// Calculate the longest paths from this node. + /// Function to convert the EdgeData to a float. + /// The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph. + /// Uses hasCycleInFGraph to check for cyclic character. If the given FGraph is not a Directed Acyclic Graph this will fail. + /// Tuples of an ordered list containing the path and the distance of the longest path. + 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 + + + /// + /// Computes longest paths from for using an inverse Dijkstra's algorithm. + /// + /// Calculate the longest paths from this node. + /// The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph. + /// If there isn't a path between two edges, the distance is set to `infinity`. + /// Compute sets all EdgeWeights to 1. If you do not want this, consider computeByEdgeData of computeByEdgeDataWith. + /// Tuples of an ordered list containing the path and the distance of the longest path. + static member compute((starting : 'NodeKey),(graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> )) = + LongestPath.getLongestPathOfFGraph starting (fun x -> 1.) graph + + /// + /// Computes longest paths from for using an inverse Dijkstra's algorithm. + /// + /// Calculate the longest paths from this node. + /// The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph. + /// If there isn't a path between two edges, the distance is set to `infinity`. + /// computeByEdgeData uses the value in EdgeData as the distance. Only usable with float weights as EdgeData. + /// Tuples of an ordered list containing the path and the distance of the longest path. + static member computeByEdgeData((starting : 'NodeKey),(graph : FGraph<'NodeKey, 'NodeData, float> )) = + LongestPath.getLongestPathOfFGraph starting id graph + + /// + /// Computes longest paths from for using an inverse Dijkstra's algorithm. + /// + /// Calculate the longest paths from this node. + /// Function to convert the EdgeData to a float. + /// The FGraph for which to compute the longest path. Has to be an Directed Acyclic Graph. + /// If there isn't a path between two edges, the distance is set to `infinity`. + /// computeByEdgeDataWith uses to convert the EdgeData into the needed float weights. + /// Tuples of an ordered list containing the path and the distance of the longest path. + static member computeByEdgeDataWith((starting : 'NodeKey),(getEdgeWeight : 'EdgeData -> float),(graph : FGraph<'NodeKey, 'NodeData, 'EdgeData> )) = + LongestPath.getLongestPathOfFGraph starting getEdgeWeight graph diff --git a/tests/Graphoscope.Tests/Graphoscope.Tests.fsproj b/tests/Graphoscope.Tests/Graphoscope.Tests.fsproj index a942876..06662d3 100644 --- a/tests/Graphoscope.Tests/Graphoscope.Tests.fsproj +++ b/tests/Graphoscope.Tests/Graphoscope.Tests.fsproj @@ -19,6 +19,7 @@ + diff --git a/tests/Graphoscope.Tests/LongestPath.fs b/tests/Graphoscope.Tests/LongestPath.fs new file mode 100644 index 0000000..26619f0 --- /dev/null +++ b/tests/Graphoscope.Tests/LongestPath.fs @@ -0,0 +1,49 @@ +module LongestPath + +open System +open Xunit +open Graphoscope +open System.IO + +open Xunit + +[] +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(fun () -> Measures.LongestPath.compute("a", cyclicGraphExample) |> ignore) + |> fun thrown -> Assert.Equal(ex.Message, thrown.Message) + +[] +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))