├── docs ├── _template.ipynb ├── img │ ├── favicon.ico │ ├── badge-script.svg │ ├── badge-binder.svg │ └── badge-notebook.svg ├── NuGet.config ├── Dockerfile ├── R_RandomGraphs.fsx ├── R_Gilbert.fsx ├── R_BarabasiAlbert.fsx ├── R_BollobasRiordan.fsx ├── A_FloydWarshall.fsx ├── index.fsx ├── M_3_Loops.fsx ├── M_2_NetworkDensity.fsx ├── A_Dijkstra.fsx ├── A_Component.fsx ├── A_Louvain.fsx ├── M_1_Degree.fsx ├── reference │ └── _template.html ├── _template.html ├── M_4_CentralityMeasures.fsx ├── G_AdjGraph_4_Intro.fsx ├── G_Digraph_2_Intro.fsx └── G_FGraph_3_Intro.fsx ├── tests ├── Graphoscope.Tests │ ├── Program.fs │ ├── ReferenceObjects.fs │ ├── ReferenceGraphs │ │ ├── zachary.txt │ │ └── out.moreno_rhesus_rhesus.txt │ ├── GraphGenerators.fs │ ├── Loop.fs │ ├── Modularity.fs │ ├── Distance.fs │ ├── BetweennessCentrality.fs │ ├── NetworkDensity.fs │ ├── LongestPath.fs │ ├── ClosenessCentrality.fs │ ├── WedgeCount.fs │ ├── Eccentricity.fs │ ├── Dijkstra.fs │ ├── Components.fs │ ├── Bfs.fs │ ├── Graphoscope.Tests.fsproj │ ├── BellmanFord.fs │ ├── FloydWarshall.fs │ ├── DFS.fs │ ├── FGraph.fs │ ├── Louvain.fs │ └── Degree.fs └── Graphoscope.Benchmark │ ├── Program.fs │ └── Graphoscope.Benchmark.fsproj ├── global.json ├── .idea └── .idea.Graphoscope.dir │ └── .idea │ ├── vcs.xml │ ├── indexLayout.xml │ └── .gitignore ├── .config └── dotnet-tools.json ├── src ├── Graphoscope.IO │ ├── Graphoscope.IO.fsproj │ └── GDF.fs └── Graphoscope │ ├── Algorithms │ ├── WedgeCount.fs │ ├── BellmanFord.fs │ ├── FloydWarshall.fs │ ├── Components.fs │ └── DFS.fs │ ├── RandomModels │ ├── CompleteGraph.fs │ ├── RegularRingLattice.fs │ ├── ErdosRenyi.fs │ ├── WattsStrogatz.fs │ ├── StarGraph.fs │ └── Gilbert.fs │ ├── Measures │ ├── Loop.fs │ ├── Size.fs │ ├── Volume.fs │ ├── MatchingIndex.fs │ ├── ClusteringCoefficient.fs │ ├── NetworkDensity.fs │ ├── Diameter.fs │ ├── Radius.fs │ ├── InformationEntropy.fs │ ├── Eccentricity.fs │ └── Modularity.fs │ └── Graphoscope.fsproj ├── LICENSE ├── .github └── workflows │ └── build-and-test.yml └── .gitignore /docs/_template.ipynb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Program.fs: -------------------------------------------------------------------------------- 1 | module Program = let [] main _ = 0 2 | -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fslaborg/Graphoscope/HEAD/docs/img/favicon.ico -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.100", 4 | "rollForward": "latestMinor" 5 | } 6 | } -------------------------------------------------------------------------------- /.idea/.idea.Graphoscope.dir/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fsdocs-tool": { 6 | "version": "16.1.1", 7 | "commands": [ 8 | "fsdocs" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/Graphoscope.Benchmark/Program.fs: -------------------------------------------------------------------------------- 1 | open BenchmarkDotNet.Running 2 | 3 | // let defaultSwitch () = BenchmarkSwitcher [|typeof; |] 4 | 5 | // [] 6 | // let Main args = 7 | // let summary = defaultSwitch().Run args 8 | // 0 9 | -------------------------------------------------------------------------------- /.idea/.idea.Graphoscope.dir/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Graphoscope.IO/Graphoscope.IO.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/.idea.Graphoscope.dir/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /projectSettingsUpdater.xml 7 | /.idea.Graphoscope.iml 8 | /modules.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/ReferenceObjects.fs: -------------------------------------------------------------------------------- 1 | module ReferenceObjects 2 | 3 | 4 | open Graphoscope 5 | 6 | 7 | let fGraph1 = 8 | [1 .. 7] |> List.fold (fun acc nk -> FGraph.addNode nk "" acc) FGraph.empty 9 | |> FGraph.addEdge 1 2 "1-2" 10 | |> FGraph.addEdge 2 3 "2-3" 11 | |> FGraph.addEdge 3 4 "3-4" 12 | |> FGraph.addEdge 4 5 "4-5" 13 | |> FGraph.addEdge 5 6 "5-6" 14 | |> FGraph.addEdge 1 7 "1-7" 15 | |> FGraph.addEdge 5 1 "5-1" -------------------------------------------------------------------------------- /tests/Graphoscope.Benchmark/Graphoscope.Benchmark.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:7.0 2 | 3 | RUN apt-get update \ 4 | && apt-get -y upgrade \ 5 | && apt-get -y install python3 python3-pip python3-dev ipython3 6 | 7 | RUN python3 -m pip install --no-cache-dir notebook jupyterlab 8 | 9 | ARG NB_USER=jovyan 10 | ARG NB_UID=1000 11 | ENV USER ${NB_USER} 12 | ENV NB_UID ${NB_UID} 13 | ENV HOME /home/${NB_USER} 14 | 15 | RUN adduser --disabled-password \ 16 | --gecos "Default user" \ 17 | --uid ${NB_UID} \ 18 | ${NB_USER} 19 | 20 | COPY . ${HOME} 21 | USER root 22 | RUN chown -R ${NB_UID} ${HOME} 23 | USER ${NB_USER} 24 | 25 | ENV PATH="${PATH}:$HOME/.dotnet/tools/" 26 | 27 | RUN dotnet tool install --global Microsoft.dotnet-interactive --version 1.0.410202 28 | 29 | RUN dotnet-interactive jupyter install 30 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/ReferenceGraphs/zachary.txt: -------------------------------------------------------------------------------- 1 | % sym unweighted 2 | % 78 34 34 3 | 1 2 4 | 1 3 5 | 2 3 6 | 1 4 7 | 2 4 8 | 3 4 9 | 1 5 10 | 1 6 11 | 1 7 12 | 5 7 13 | 6 7 14 | 1 8 15 | 2 8 16 | 3 8 17 | 4 8 18 | 1 9 19 | 3 9 20 | 3 10 21 | 1 11 22 | 5 11 23 | 6 11 24 | 1 12 25 | 1 13 26 | 4 13 27 | 1 14 28 | 2 14 29 | 3 14 30 | 4 14 31 | 6 17 32 | 7 17 33 | 1 18 34 | 2 18 35 | 1 20 36 | 2 20 37 | 1 22 38 | 2 22 39 | 24 26 40 | 25 26 41 | 3 28 42 | 24 28 43 | 25 28 44 | 3 29 45 | 24 30 46 | 27 30 47 | 2 31 48 | 9 31 49 | 1 32 50 | 25 32 51 | 26 32 52 | 29 32 53 | 3 33 54 | 9 33 55 | 15 33 56 | 16 33 57 | 19 33 58 | 21 33 59 | 23 33 60 | 24 33 61 | 30 33 62 | 31 33 63 | 32 33 64 | 9 34 65 | 10 34 66 | 14 34 67 | 15 34 68 | 16 34 69 | 19 34 70 | 20 34 71 | 21 34 72 | 23 34 73 | 24 34 74 | 27 34 75 | 28 34 76 | 29 34 77 | 30 34 78 | 31 34 79 | 32 34 80 | 33 34 81 | -------------------------------------------------------------------------------- /src/Graphoscope/Algorithms/WedgeCount.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Algorithms 2 | 3 | open Graphoscope 4 | 5 | type WedgeCount() = 6 | static member ofGraph(graph: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 7 | graph.Keys 8 | |> Seq.sumBy (fun key -> 9 | let node = graph[key] 10 | let successors = node |> FContext.successors |> Seq.map fst 11 | let predecessors = node |> FContext.predecessors |> Seq.map fst 12 | // exclude self loops and double edges 13 | let exclude = Seq.length (successors |> Seq.filter (fun x -> 14 | let isDoubleEdge = predecessors |> Seq.contains x 15 | let isSelfLoop = x = key 16 | isDoubleEdge || isSelfLoop)) 17 | let n = Seq.length successors + Seq.length predecessors - exclude 18 | let wedges = n * (n - 1) / 2 19 | wedges) 20 | -------------------------------------------------------------------------------- /src/Graphoscope/RandomModels/CompleteGraph.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.RandomModels 2 | 3 | open Graphoscope 4 | 5 | type CompleteGraph() = 6 | 7 | /// 8 | /// Returns a complete undirected graph. Every node is connected to every node. 9 | /// 10 | /// specifies how many additional nodes the final graph will have. 11 | /// An UndirectedGraph 12 | static member init (nodesToCreate: int) = 13 | 14 | let nodeIds = Array.init nodesToCreate id 15 | let g = 16 | UndirectedGraph.empty 17 | |> UndirectedGraph.addNodes (nodeIds |> Array.map(fun i -> i, i)) 18 | 19 | nodeIds |> Seq.allPairs nodeIds 20 | |> Seq.filter(fun (o,t) -> o<>t) 21 | |> Seq.map(fun (f,t)-> f,t,1.0) 22 | |> Seq.toArray 23 | |> fun e -> UndirectedGraph.addEdges e g 24 | -------------------------------------------------------------------------------- /docs/R_RandomGraphs.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Random Graph generation 4 | category: Random Graph 5 | categoryindex: 4 6 | index: 1 7 | --- 8 | 9 | # Random graph models 10 | ## Motivation and overview 11 | In every implementation workflow, there comes the point where you have to test wether everything works as expexted or not. 12 | For this, a matching test set is necessary. In some cases (e.g. List sorting algorithms) creating those test sets is done in a matter of seconds. 13 | In other cases, especially if the data you work with is more than one dimensional, it can get quite tedious. 14 | To this effect, FSharp.FGl comes equipped with implementations of random graph generation models. 15 | In this tutorial I want to introduce you to the models implemented and how to generate graphs with the given functions: 16 | 17 | * Gilbert model 18 | 19 | * Barabási-Albert model 20 | 21 | * Bollobás-Riordan methode 22 | *) -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/GraphGenerators.fs: -------------------------------------------------------------------------------- 1 | module GraphGenerators 2 | 3 | 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.RandomModels 7 | 8 | [] 9 | let ``Watts Strogatz generates correctly`` () = 10 | 11 | let g = WattsStrogatz.initWattsStrogatz 16 4 0.2 12 | 13 | let edgesCount = UndirectedGraph.getAllEdges g |> Array.length 14 | let nodeCount = UndirectedGraph.getNodes g |> Array.length 15 | 16 | Assert.Equal(16, nodeCount) 17 | Assert.Equal(32, edgesCount) 18 | 19 | [] 20 | let ``Stargraph generates correctly`` () = 21 | 22 | let g = StarGraph.initStarFGraph 100 id id (fun a b -> 1.) 23 | 24 | let edgesCount = Measures.Volume.volumeOfFGraph g 25 | let nodeCount = Measures.Size.sizeOfFGraph g 26 | 27 | let degreeMin = Measures.Degree.minimum g 28 | let degreeMax = Measures.Degree.maximum g 29 | 30 | Assert.Equal(100, nodeCount) 31 | Assert.Equal(99, edgesCount) 32 | Assert.Equal(1,degreeMin) 33 | Assert.Equal(99,degreeMax) -------------------------------------------------------------------------------- /src/Graphoscope/RandomModels/RegularRingLattice.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.RandomModels 2 | 3 | open Graphoscope 4 | 5 | type RegularRingLattice() = 6 | 7 | /// 8 | /// Returns an Undirected graph in a ring lattice in whichall node have the same degree and there are no self loops. 9 | /// 10 | /// Specifies the number of nodes 11 | /// Specifies the degree of the nodes 12 | /// An UndirectedGraph 13 | 14 | static member initUndirectedGraph (n: int) (k: int) = 15 | // Following standard practice, 16 | // odd values of k will be rounded down to the previous even integer. 17 | let g: UndirectedGraph = 18 | UndirectedGraph.empty 19 | |> UndirectedGraph.addNodes (Array.init n (fun i -> i, i)) 20 | 21 | // Connect each node to its half-k succeeding neighbors 22 | for i in 0 .. n-1 do 23 | for j in i+1 .. i+(k/2) do 24 | UndirectedGraph.addEdge (i, j%n, 1.) g|>ignore 25 | g -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 FsLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: [ developer ] 6 | pull_request: 7 | branches: [ developer ] 8 | 9 | 10 | jobs: 11 | build-and-test-linux: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v3 19 | with: 20 | dotnet-version: 6.x.x 21 | - name: make script executable 22 | run: chmod u+x build.sh 23 | - name: Build and test 24 | working-directory: ./ 25 | run: ./build.sh runTestsWithCodeCov 26 | - name: Upload coverage reports to Codecov 27 | uses: codecov/codecov-action@v3 28 | with: 29 | directory: "./TestResults/" 30 | token: ${{ secrets.CODECOV_TOKEN }} 31 | 32 | build-and-test-windows: 33 | 34 | runs-on: windows-latest 35 | 36 | steps: 37 | - uses: actions/checkout@v3 38 | - name: Setup .NET 39 | uses: actions/setup-dotnet@v3 40 | with: 41 | dotnet-version: 6.x.x 42 | - name: Build and test 43 | working-directory: ./ 44 | run: ./build.cmd runtests 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/R_Gilbert.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Gilbert 4 | category: Random Graph 5 | categoryindex: 4 6 | index: 2 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpx.Collections, 3.1.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: FSharp.Data, 6.2.0" 17 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 18 | 19 | (*** condition: ipynb ***) 20 | #if IPYNB 21 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 22 | #endif // IPYNB 23 | 24 | (** 25 | # Random graph models 26 | ## Gilbert model 27 | 28 | The gilbert model (or G(N,p) model) was introduced by Edgar Gilbert in 1959. In this model, you assign a fixed amount of vertices N and a probability p. 29 | p denotes the probality, that edge between two vertices exists or not. 30 | *) 31 | open Graphoscope 32 | open Graphoscope.RandomModels 33 | let N = 50 34 | let p = 0.5 35 | 36 | let myGilbertGraph = Gilbert.initDirectedFGraph N p 37 | 38 | (***hide***) 39 | let g = sprintf "You have created a graph with %i nodes and %i edges"(FGraph.countNodes myGilbertGraph) (FGraph.countEdges myGilbertGraph) 40 | (*** include-value: g ***) 41 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Loop.fs: -------------------------------------------------------------------------------- 1 | module Loop 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | [] 11 | let ``Monkey graph import has correct measures`` () = 12 | //measures taken from http://konect.cc/networks/moreno_rhesus/ 13 | let file = Path.Combine(Environment.CurrentDirectory, "ReferenceGraphs/out.moreno_rhesus_rhesus.txt") 14 | 15 | let monkeyGraph = 16 | File.ReadLines file 17 | |> Seq.skip 2 18 | |> Seq.map 19 | (fun str -> 20 | let arr = str.Split(' ') 21 | int arr.[0], arr.[0], int arr.[1], arr.[1], float arr.[2]) 22 | |> FGraph.ofSeq 23 | 24 | Assert.Equal(0.,(Measures.Loop.loopCountFGraph monkeyGraph)) 25 | 26 | [] 27 | let ``Fully looped graph has correct measures`` () = 28 | let nodes = [0 .. 10] 29 | let edges = 30 | nodes|>List.map(fun x -> x,x,1.) 31 | 32 | let counts: obj = nodes.Length 33 | 34 | let g = FGraph.empty|>FGraph.addNodes (nodes|>List.map(fun x -> x,x))|>FGraph.addEdges edges 35 | 36 | Assert.Equal(counts,(Measures.Loop.loopCountFGraph g)) 37 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Modularity.fs: -------------------------------------------------------------------------------- 1 | module Modularity 2 | 3 | open Xunit 4 | open Graphoscope 5 | open FSharpAux 6 | 7 | [] 8 | let ``UndirectedGraph modularity works correctly `` () = 9 | let e = 10 | [| 11 | ("a", "b", 1.0) 12 | ("a", "c", 1.0) 13 | ("b", "c", 1.0) 14 | ("b", "d", 1.0) // inter-community edge 15 | ("d", "e", 1.0) 16 | ("d", "f", 1.0) 17 | ("d", "g", 1.0) 18 | ("f", "g", 1.0) 19 | ("f", "e", 1.0) 20 | |] 21 | 22 | let g = UndirectedGraph.createFromEdges e 23 | let partition = [|set ["a"; "b"; "c"]; set ["d"; "e"; "f"; "g"]|] 24 | 25 | let actual = Measures.Modularity.ofUndirectedGraph id 1. partition g 26 | let expected = ((3./9.) - (7./18.)**2) + ((5./9.) - (11./18.)**2) 27 | 28 | Assert.Equal(expected, actual, 15) 29 | 30 | [] 31 | let ``DiGraph modularity works correctly `` () = 32 | let g = DiGraph.createFromEdges [|(2,1,1.); (2,3,1.); (3,4,1.)|] 33 | let partition = [|set [1; 2]; set [3; 4]|] 34 | 35 | let actual = Measures.Modularity.ofDiGraph id 1. partition g 36 | let expected = 2. / 9. 37 | 38 | Assert.Equal(expected, actual) 39 | -------------------------------------------------------------------------------- /src/Graphoscope/RandomModels/ErdosRenyi.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.RandomModels 2 | 3 | open System 4 | open Graphoscope 5 | 6 | type ErdosRenyi() = 7 | 8 | 9 | /// 10 | /// Returns a Erdos-Renyi graph https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93R%C3%A9nyi_model 11 | /// 12 | /// Specifies the number of nodes 13 | /// Specifies the number of edges 14 | 15 | /// An UndirectedGraph 16 | /// 17 | static member init (nodes:int) (edges:int) = 18 | let rng = System.Random() 19 | let g = 20 | UndirectedGraph.empty 21 | |> UndirectedGraph.addNodes (Array.init nodes (fun i -> i, i)) 22 | 23 | let createRandomEdge (nodeCount: int)= 24 | let f = rng.Next(nodeCount) 25 | let s = rng.Next(nodeCount) 26 | Math.Min(f,s), Math.Max(f,s) 27 | 28 | Seq.initInfinite(fun _ -> createRandomEdge nodes) 29 | |> Seq.filter(fun (o,t) -> o<>t) 30 | |> Seq.distinct 31 | |> Seq.take edges 32 | |> Seq.map(fun (f,t)-> f,t,1.0) 33 | |> Seq.toArray 34 | |> fun e -> UndirectedGraph.addEdges e g -------------------------------------------------------------------------------- /docs/R_BarabasiAlbert.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Barabási-Albert 4 | category: Random Graph 5 | categoryindex: 4 6 | index: 4 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpx.Collections, 3.1.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: FSharp.Data, 6.2.0" 17 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 18 | 19 | (*** condition: ipynb ***) 20 | #if IPYNB 21 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 22 | #endif // IPYNB 23 | 24 | (** 25 | # Random graph models 26 | ## Barabási-Albert model 27 | 28 | The Barabási-Albert (BA) model is a popular network growth model used to generate random scale-free networks. 29 | It provides a valuable tool for generating synthetic networks that exhibit similar properties to many natural and man-made networks, mainly a scale-free character. 30 | *) 31 | 32 | open Graphoscope 33 | open Graphoscope.RandomModels 34 | let N = 50 35 | 36 | let edgesPerIteration = 5 37 | 38 | // let myBarabasiAlbert = RandomModels.BarabasiAlbert.initFGraph N edgesPerIteration id id (fun x -> 1.) FGraph.empty 39 | 40 | //printfn"You have created a graph with %i nodes and %i edges"(FGraph.countNodes myBarabasiAlbert) (FGraph.countEdges myBarabasiAlbert) 41 | 42 | -------------------------------------------------------------------------------- /docs/R_BollobasRiordan.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Bollobas-Riordan 4 | category: Random Graph 5 | categoryindex: 4 6 | index: 3 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpx.Collections, 3.1.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: FSharp.Data, 6.2.0" 17 | #r "nuget: Plotly.NET, 4.1.0" 18 | #r "nuget: Plotly.NET.Interactive, 4.1.0" 19 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 20 | 21 | (*** condition: ipynb ***) 22 | #if IPYNB 23 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 24 | #endif // IPYNB 25 | 26 | (** 27 | # Random graph models 28 | ## Bollobás-Riordan methode 29 | The Bollobás-Riordan method, proposed by mathematicians Béla Bollobás and Oliver Riordan, is a procedure for generating random graphs with a given degree sequence 30 | It allows researchers to explore and analyze the properties of large-scale networks and complex systems in a stochastic setting. 31 | *) 32 | open Graphoscope 33 | open Graphoscope.RandomModels 34 | let N = 50 35 | 36 | let myBollobasRiordan = RandomModels.BollobasRiordan.initDirectedFGraph N 0.1 0.6 0.3 0.6 0.4 FGraph.empty 37 | 38 | (***hide***) 39 | let g = sprintf "You have created a graph with %i nodes and %i edges"(FGraph.countNodes myBollobasRiordan) (FGraph.countEdges myBollobasRiordan) 40 | (*** include-value: g ***) 41 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Distance.fs: -------------------------------------------------------------------------------- 1 | module Distance 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | // //Contains Betweenness, Closeness Eccentricity and Distance Metrics 11 | // [] 12 | // let ``Simple example yields the correct metrics`` () = 13 | 14 | // //Simple graph example 15 | // let edges = 16 | // seq{ 17 | // 1,1,2,2,1. 18 | // 1,1,4,4,1. 19 | // 1,1,5,5,1. 20 | // 1,1,6,6,1. 21 | // 2,2,3,3,1. 22 | // } 23 | 24 | // let graph = UndirectedFGraph.ofSeq edges 25 | 26 | // let graph2d = FGraph.toArray2D(fun x -> x-1) graph 27 | 28 | // let floydWarshall = Algorithms.FloydWarshall.fromArray2D graph2d 29 | 30 | // let distanceAverage,distanceMax,distanceMin = 31 | // 2.3333333333333335, 32 | // 3., 33 | // 1. 34 | 35 | // let distanceAveragePred,distanceMaxPred,distanceMinPred = 36 | // Measures.Distance.averageOfFloydWarshall floydWarshall, 37 | // Measures.Distance.maxOfFloydWarshall floydWarshall, 38 | // Measures.Distance.minOfFloydWarshall floydWarshall 39 | 40 | // Assert.Equal(distanceAverage,distanceAveragePred) 41 | // Assert.Equal(distanceMax,distanceMaxPred) 42 | // Assert.Equal(distanceMin,distanceMinPred) 43 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/BetweennessCentrality.fs: -------------------------------------------------------------------------------- 1 | module BetweennessCentrality 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | // //Contains Betweenness, Closeness Eccentricity and Distance Metrics 11 | // [] 12 | // let ``Simple example yields the correct metrics`` () = 13 | 14 | // //Simple graph example 15 | // let edges = 16 | // seq{ 17 | // 1,1,2,2,1. 18 | // 1,1,4,4,1. 19 | // 1,1,5,5,1. 20 | // 1,1,6,6,1. 21 | // 2,2,3,3,1. 22 | // } 23 | 24 | // let graph = FGraph.ofSeq edges 25 | 26 | // let betweennessMap = 27 | // seq { 28 | // 1,0.600 29 | // 2,0.267 30 | // 3,0.000 31 | // 4,0.000 32 | // 5,0.000 33 | // 6,0.000 34 | // } 35 | // |>Map.ofSeq 36 | 37 | // let betweennessPaths = 38 | // Measures.BetweennessCentrality.returnPaths (Algorithms.Dijkstra.ofUndirectedFGraphIncludingPath) id id graph 39 | 40 | // let betweennessTest = 41 | // graph.Keys 42 | // |> Seq.map(fun x -> 43 | // let predicted = Measures.BetweennessCentrality.getBetweennessOfPathsAndNode betweennessPaths id x |> Math.round 3 44 | // let actual = betweennessMap.Item x 45 | // predicted=actual 46 | // ) 47 | 48 | // Assert.DoesNotContain(false,betweennessTest) 49 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/NetworkDensity.fs: -------------------------------------------------------------------------------- 1 | module GraphDensity 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | // [] 11 | // let ``Monkey graph import has correct measures`` () = 12 | // //measures taken from http://konect.cc/networks/moreno_rhesus/ 13 | // let file = Path.Combine(Environment.CurrentDirectory, "ReferenceGraphs/out.moreno_rhesus_rhesus.txt") 14 | 15 | 16 | // let monkeyGraph = 17 | // File.ReadLines file 18 | // |> Seq.skip 2 19 | // |> Seq.map 20 | // (fun str -> 21 | // let arr = str.Split(' ') 22 | // int arr.[0], arr.[0], int arr.[1], arr.[1], float arr.[2]) 23 | // |> AdjGraph.ofSeq 24 | 25 | // //Assert.Equal(111.0, (Measures.getVolume monkeyGraph)) 26 | // Assert.Equal(0.925,(Measures.GraphDensity.ofAdjGraph monkeyGraph)) 27 | // //Assert.Equal(13.8750,(Measures.GraphDensity.ofFGraph monkeyGraph)) 28 | 29 | [] 30 | let ``Fully connected graph has correct measures`` () = 31 | let nodes = [0 .. 10] 32 | let edges = 33 | [for n=0 to 10 do 34 | for i=n+1 to 10 do 35 | nodes.[n],nodes.[i],1. 36 | ] 37 | 38 | let g = 39 | AdjGraph.empty 40 | |>AdjGraph.addNodes (nodes|>List.map(fun x -> x,x)) 41 | |>AdjGraph.addEdges edges 42 | 43 | Assert.Equal(1.,(Measures.GraphDensity.ofAdjGraph g)) 44 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/LongestPath.fs: -------------------------------------------------------------------------------- 1 | module LongestPath 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open System.IO 7 | 8 | open Xunit 9 | 10 | [] 11 | let ``smallCyclicGraphReturnsExceptedResults``() = 12 | let cyclicGraphExample = 13 | seq{ 14 | "a","b",1. 15 | "a","c",1. 16 | "a","d",15. 17 | "a","e",1. 18 | "c","e",1. 19 | "b","d",13. 20 | "d","e",1. 21 | "e","b",1. 22 | "d","d",2. 23 | } 24 | |> Seq.map(fun (s, t, w) -> (s, s, t, t, w)) 25 | |> FGraph.ofSeq 26 | 27 | let ex = new System.Exception("The given FGraph is not a Directed Acyclic Graph!") 28 | 29 | // Assert 30 | Assert.Throws(fun () -> Measures.LongestPath.compute("a", cyclicGraphExample) |> ignore) 31 | |> fun thrown -> Assert.Equal(ex.Message, thrown.Message) 32 | 33 | [] 34 | let smallAcyclicGraphReturnsExceptedResults () = 35 | 36 | let acyclicGraphExample = 37 | seq{ 38 | "A","B",2. 39 | "A","D",1. 40 | "B","C",2. 41 | "D","C",17.2 42 | "A","C",18. 43 | 44 | } 45 | |>Seq.map(fun (s,t,w) -> (s,s,t,t,w)) 46 | |>FGraph.ofSeq 47 | 48 | Assert.Equal((["A"; "D"; "C"], 18.2),Measures.LongestPath.computeByEdgeData("A",acyclicGraphExample)) 49 | Assert.Equal((["A"; "B"; "C"], 2.),Measures.LongestPath.compute("A",acyclicGraphExample)) 50 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/ReferenceGraphs/out.moreno_rhesus_rhesus.txt: -------------------------------------------------------------------------------- 1 | % asym posweighted 2 | % 111 16 16 3 | 1 2 3 4 | 1 3 4 5 | 4 2 1 6 | 5 6 15 7 | 5 7 8 8 | 5 8 2 9 | 5 3 2 10 | 5 9 1 11 | 5 10 9 12 | 5 11 3 13 | 5 12 1 14 | 5 13 4 15 | 5 14 2 16 | 6 1 17 17 | 6 5 5 18 | 6 8 8 19 | 6 3 1 20 | 6 10 4 21 | 6 12 3 22 | 6 15 1 23 | 7 5 5 24 | 7 8 2 25 | 7 2 1 26 | 7 9 2 27 | 7 14 11 28 | 16 2 2 29 | 16 3 2 30 | 16 9 1 31 | 16 11 3 32 | 16 12 1 33 | 16 15 3 34 | 8 6 11 35 | 8 7 1 36 | 2 1 49 37 | 2 4 3 38 | 2 16 1 39 | 2 3 41 40 | 2 9 3 41 | 2 10 2 42 | 2 11 1 43 | 2 12 6 44 | 2 15 9 45 | 3 1 25 46 | 3 2 8 47 | 3 9 5 48 | 3 10 1 49 | 3 11 9 50 | 3 12 2 51 | 3 15 21 52 | 3 13 2 53 | 9 2 4 54 | 9 3 6 55 | 9 11 4 56 | 9 15 1 57 | 9 13 2 58 | 9 14 6 59 | 10 1 1 60 | 10 5 5 61 | 10 6 3 62 | 10 2 4 63 | 10 3 4 64 | 10 9 2 65 | 10 11 8 66 | 10 12 5 67 | 10 15 11 68 | 10 13 16 69 | 11 1 5 70 | 11 5 1 71 | 11 16 5 72 | 11 2 9 73 | 11 3 7 74 | 11 9 4 75 | 11 10 1 76 | 11 15 10 77 | 11 13 1 78 | 12 5 2 79 | 12 6 1 80 | 12 8 3 81 | 12 2 3 82 | 12 3 24 83 | 12 10 4 84 | 12 11 5 85 | 12 15 25 86 | 12 13 2 87 | 12 14 1 88 | 15 4 1 89 | 15 16 4 90 | 15 2 6 91 | 15 3 23 92 | 15 10 4 93 | 15 11 13 94 | 15 12 2 95 | 15 14 1 96 | 13 1 1 97 | 13 5 2 98 | 13 3 9 99 | 13 9 2 100 | 13 10 21 101 | 13 11 3 102 | 13 12 4 103 | 13 15 5 104 | 14 4 3 105 | 14 7 1 106 | 14 8 1 107 | 14 2 1 108 | 14 3 1 109 | 14 9 8 110 | 14 10 1 111 | 14 12 5 112 | 14 15 2 113 | 14 13 1 -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/ClosenessCentrality.fs: -------------------------------------------------------------------------------- 1 | module ClosenessCentrality 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | // //Contains Betweenness, Closeness Eccentricity and Distance Metrics 11 | // [] 12 | // let ``Simple example yields the correct metrics`` () = 13 | 14 | // //Simple graph example 15 | // let edges = 16 | // seq{ 17 | // 1,1,2,2,1. 18 | // 1,1,4,4,1. 19 | // 1,1,5,5,1. 20 | // 1,1,6,6,1. 21 | // 2,2,3,3,1. 22 | // } 23 | 24 | // let graph = FGraph.ofSeq edges 25 | 26 | // let closeness = 27 | // seq { 28 | // 1,0.833 29 | // 2,0.625 30 | // 3,0.417 31 | // 4,0.500 32 | // 5,0.500 33 | // 6,0.500 34 | // }|>Map.ofSeq 35 | 36 | // let closenessPredicted = 37 | // Measures.ClosenessCentrality.ofFGraphNormalised (Algorithms.Dijkstra.ofUndirectedFGraph) id graph 38 | // |> Seq.map(fun x -> x.Key, Math.round 3 x.Value)|>Map.ofSeq 39 | 40 | // let closenessTest = 41 | // seq{ 42 | // for i in graph.Keys do 43 | // let close = closeness.Item i 44 | // let closePred = closenessPredicted.Item i 45 | // (close-closePred) 46 | // |>abs 47 | // |>fun x -> x<0.001 48 | // } 49 | 50 | // Assert.DoesNotContain(false,closenessTest) 51 | -------------------------------------------------------------------------------- /src/Graphoscope/RandomModels/WattsStrogatz.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.RandomModels 2 | 3 | 4 | open Graphoscope 5 | 6 | type WattsStrogatz() = 7 | 8 | 9 | 10 | /// 11 | /// Returns a Watts-Strogatz graph https://en.wikipedia.org/wiki/Watts%E2%80%93Strogatz_model 12 | /// 13 | /// Specifies the number of nodes 14 | /// Specifies the degree of the nodes 15 | /// Specifies the rewiring probability 16 | /// An UndirectedGraph 17 | /// 18 | static member initWattsStrogatz (n: int) (k: int) (p:float) = 19 | 20 | let rng = System.Random() 21 | let rec rndExclude (nodeCount: int) (excl: int array) = 22 | 23 | let r = rng.Next(nodeCount) 24 | 25 | if excl |> Array.contains r then 26 | rndExclude nodeCount excl 27 | else 28 | r 29 | 30 | let g = RegularRingLattice.initUndirectedGraph n k 31 | 32 | // rewire 33 | g 34 | |> UndirectedGraph.getAllEdges 35 | |> Array.map(fun (f,t, _) -> 36 | if rng.NextDouble() < p then 37 | UndirectedGraph.removeEdge (f,t) g |> ignore 38 | let existingTargets = UndirectedGraph.getEdges f g |> Array.map(fun (tar , _)-> tar) 39 | let excl = existingTargets |> Array.append [|f;t|] 40 | UndirectedGraph.addEdge (f , (rndExclude n excl), 1.0) g |> ignore 41 | ) |> ignore 42 | 43 | g -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/WedgeCount.fs: -------------------------------------------------------------------------------- 1 | module WedgeCount 2 | 3 | open Xunit 4 | open Graphoscope 5 | 6 | [] 7 | let ``Wedge Count algorithm test graph without cycles`` () = 8 | // the wedges in the graph below are 9 | // 1, 8 and 1, 3 10 | // 1, 3 and 1, 2 11 | // 1, 2 and 2, 5 12 | // 1, 2 and 2, 4 13 | // 1, 3 and 3, 6 14 | // 1, 2 and 1, 8 15 | // 2, 5 and 2, 4 16 | // 2, 5 and 5, 7 17 | let dummyEdgeData = 0.1 18 | let actual = 19 | FGraph.empty 20 | |> FGraph.addElement 1 "Node 1" 2 "Node 2" dummyEdgeData 21 | |> FGraph.addElement 1 "Node 1" 3 "Node 3" dummyEdgeData 22 | |> FGraph.addElement 1 "Node 1" 8 "Node 8" dummyEdgeData 23 | |> FGraph.addElement 2 "Node 2" 4 "Node 4" dummyEdgeData 24 | |> FGraph.addElement 2 "Node 2" 5 "Node 5" dummyEdgeData 25 | |> FGraph.addElement 3 "Node 3" 6 "Node 6" dummyEdgeData 26 | |> FGraph.addElement 5 "Node 5" 7 "Node 7" dummyEdgeData 27 | |> Algorithms.WedgeCount.ofGraph 28 | let expected = 8 29 | Assert.Equal(expected, actual) 30 | 31 | [] 32 | let ``Wedge Count algorithm test triangle cycle`` ()= 33 | // the wedges in the graph below are 34 | // 1, 2 and 2, 3 35 | // 1, 2 and 1, 3 36 | // 2, 3 and 1, 3 37 | let actual = 38 | FGraph.empty 39 | |> FGraph.addElement 1 "Node 1" 2 "Node 2" 0.1 40 | |> FGraph.addElement 2 "Node 2" 3 "Node 3" 0.1 41 | |> FGraph.addElement 3 "Node 3" 1 "Node 1" 0.1 42 | |> Algorithms.WedgeCount.ofGraph 43 | 44 | let expected = 3 45 | Assert.Equal(expected, actual) -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Eccentricity.fs: -------------------------------------------------------------------------------- 1 | module EccentricityCentrality 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | // //Contains Betweenness, Closeness Eccentricity and Distance Metrics 11 | // [] 12 | // let ``Simple example yields the correct metrics`` () = 13 | // 14 | // 15 | // 16 | // 17 | // //Simple graph example 18 | // let edges = 19 | // seq{ 20 | // 1,1,2,2,1. 21 | // 1,1,4,4,1. 22 | // 1,1,5,5,1. 23 | // 1,1,6,6,1. 24 | // 2,2,3,3,1. 25 | // } 26 | 27 | // let graph = UndirectedFGraph.ofSeq edges 28 | 29 | // let graph2d = UndirectedFGraph.toArray2D (fun x -> x-1) graph 30 | 31 | // let floydWarshall = Algorithms.FloydWarshall.fromArray2D graph2d 32 | 33 | // let eccentricityMap = 34 | // seq { 35 | // 1,2 36 | // 2,2 37 | // 3,3 38 | // 4,3 39 | // 5,3 40 | // 6,3 41 | // } 42 | // |>Map.ofSeq 43 | 44 | // let eccentricityPredicted = 45 | // Measures.Eccentricity.ofFGraph2D floydWarshall 46 | 47 | // let eccentricityTest = 48 | // seq{ 49 | // for (kvp) in eccentricityPredicted do 50 | // let key,valu = kvp.Key,kvp.Value 51 | // let actualVal = eccentricityMap.Item (key+1) 52 | // valu=actualVal 53 | // } 54 | 55 | // Assert.DoesNotContain(false,eccentricityTest) 56 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Dijkstra.fs: -------------------------------------------------------------------------------- 1 | module Dijkstra 2 | 3 | open Xunit 4 | open Graphoscope 5 | open FSharpAux 6 | 7 | 8 | 9 | [] 10 | let ``Dijkstra simple example on FGraph works correctly`` () = 11 | 12 | let dijkstraTestGraph2 = 13 | FGraph.empty 14 | |> FGraph.addElement 0 "Node 0" 4 "Node 4" 7. 15 | |> FGraph.addElement 3 "Node 3" 0 "Node 0" 3. 16 | |> FGraph.addElement 4 "Node 4" 3 "Node 3" 1. 17 | |> FGraph.addElement 4 "Node 4" 1 "Node 1" 2. 18 | |> FGraph.addElement 2 "Node 2" 4 "Node 4" 5. 19 | |> FGraph.addElement 2 "Node 2" 3 "Node 3" 9. 20 | |> FGraph.addElement 1 "Node 1" 2 "Node 2" 4. 21 | 22 | let actual = Algorithms.Dijkstra.ofFGraph 2 id dijkstraTestGraph2 23 | 24 | // DFS Traversal Result (starting node 1): 25 | let expected = [(0, 9.0); (4, 5.0); (3, 6.0); (1, 7.0); (2, 0.0)] |> Set.ofList 26 | 27 | Assert.Equal>(expected, actual |> Dictionary.toSeq |> Set.ofSeq) 28 | 29 | [] 30 | let ``Dijkstra simple example on DiGraph works correctly`` () = 31 | 32 | let dijkstraTestGraph2 = 33 | DiGraph.empty 34 | |> DiGraph.addElement 0 "Node 0" 4 "Node 4" 7. 35 | |> DiGraph.addElement 3 "Node 3" 0 "Node 0" 3. 36 | |> DiGraph.addElement 4 "Node 4" 3 "Node 3" 1. 37 | |> DiGraph.addElement 4 "Node 4" 1 "Node 1" 2. 38 | |> DiGraph.addElement 2 "Node 2" 4 "Node 4" 5. 39 | |> DiGraph.addElement 2 "Node 2" 3 "Node 3" 9. 40 | |> DiGraph.addElement 1 "Node 1" 2 "Node 2" 4. 41 | 42 | let actual = Algorithms.Dijkstra.ofDiGraph 2 id dijkstraTestGraph2 43 | 44 | // DFS Traversal Result (starting node 1): 45 | let expected = [(0, 9.0); (4, 5.0); (3, 6.0); (1, 7.0); (2, 0.0)] |> Set.ofList 46 | 47 | Assert.Equal>(expected, actual |> Set.ofArray) 48 | -------------------------------------------------------------------------------- /docs/A_FloydWarshall.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Floyd-Warshall 4 | category: Algorithms 5 | categoryindex: 3 6 | index: 2 7 | --- 8 | *) 9 | 10 | 11 | (*** hide ***) 12 | 13 | (*** condition: prepare ***) 14 | #r "nuget: FSharpAux.Core, 2.0.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 18 | 19 | (*** condition: ipynb ***) 20 | #if IPYNB 21 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 22 | #endif // IPYNB 23 | 24 | (** 25 | # Introducing the Floyd-Warshall Algorithm for All-Pairs Shortest Path in Graphs 26 | The Floyd-Warshall algorithm is a widely used algorithm in graph theory and computer science for finding the shortest paths between all pairs of vertices in a weighted graph. It is named after its inventors, Robert Floyd and Stephen Warshall, who independently proposed it in the early 1960s. 27 | 28 | The algorithm works on both directed and undirected graphs, where edges have non-negative weights (can be zero or positive but not negative). The goal is to find the shortest path distance between all pairs of vertices in the graph. 29 | 30 | 31 | Floyd-Warshall and Dijkstra's algorithm are both used to find the shortest paths in a graph, but they serve different purposes and have different use cases. 32 | 33 | They are not direct alternatives to each other; rather, they are used in different scenarios based on the problem requirements and the characteristics of the graph. 34 | *) 35 | 36 | open Graphoscope 37 | open Cytoscape.NET 38 | 39 | let dwgDiGraph = 40 | let nodes = [|0;1;2;3;4;5|]|>Array.map(fun x -> x,x) 41 | let edges = [|0,1,7.;0,2,12.;1,2,2.;1,3,9.;2,4,10.;4,3,4.;3,5,1.;4,5,5.|] 42 | DiGraph.createFromNodes nodes 43 | |>DiGraph.addEdges edges 44 | 45 | 46 | let dijDiGraph = Algorithms.FloydWarshall.fromJaggedArray (DiGraph.toMatrix dwgDiGraph) 47 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Components.fs: -------------------------------------------------------------------------------- 1 | 2 | module Components 3 | 4 | open Xunit 5 | open Graphoscope 6 | open FSharpAux 7 | 8 | 9 | let testGraph = 10 | DiGraph.empty 11 | |> DiGraph.addNode 1 1 12 | |> DiGraph.addNode 2 2 13 | |> DiGraph.addNode 3 3 14 | |> DiGraph.addNode 4 4 15 | |> DiGraph.addNode 5 5 16 | |> DiGraph.addNode 6 6 17 | |> DiGraph.addNode 7 7 18 | |> DiGraph.addEdge (1, 2, 1.0) 19 | |> DiGraph.addEdge (1, 3, 1.0) 20 | |> DiGraph.addEdge (2, 4, 1.0) 21 | |> DiGraph.addEdge (5, 6, 1.0) 22 | |> DiGraph.addEdge (5, 7, 1.0) 23 | 24 | 25 | let testGraphGiant = 26 | DiGraph.empty 27 | |> DiGraph.addNode 1 1 28 | |> DiGraph.addNode 2 2 29 | |> DiGraph.addNode 3 3 30 | |> DiGraph.addNode 4 4 31 | |> DiGraph.addNode 5 5 32 | |> DiGraph.addNode 6 6 33 | |> DiGraph.addNode 7 7 34 | |> DiGraph.addEdge (1, 2, 1.0) 35 | |> DiGraph.addEdge (1, 3, 1.0) 36 | |> DiGraph.addEdge (2, 4, 1.0) 37 | |> DiGraph.addEdge (4, 5, 1.0) 38 | |> DiGraph.addEdge (5, 6, 1.0) 39 | |> DiGraph.addEdge (5, 7, 1.0) 40 | 41 | [] 42 | let ``Can detect no giant compenent`` () = 43 | testGraph 44 | |> Algorithms.Components.isWeakComponentOfDiGraph 45 | |> Assert.False 46 | 47 | [] 48 | let ``Can detect giant compenent`` () = 49 | testGraphGiant 50 | |> Algorithms.Components.isWeakComponentOfDiGraph 51 | |> Assert.True 52 | 53 | [] 54 | let ``Can get components`` () = 55 | let components = 56 | testGraph 57 | |> Algorithms.Components.getWeakComponentsOfDiGraph 58 | |> Set.count 59 | 60 | Assert.True (2 = components) 61 | 62 | [] 63 | let ``Can get new graph of largest component`` () = 64 | let newGraph = 65 | testGraph 66 | |> Algorithms.Components.getLargestWeakComponentOfDiGraph 67 | 68 | Assert.True (3 = (DiGraph.countEdges newGraph) ) 69 | -------------------------------------------------------------------------------- /docs/img/badge-script.svg: -------------------------------------------------------------------------------- 1 | Download scriptDownload script -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Bfs.fs: -------------------------------------------------------------------------------- 1 | module BFS 2 | 3 | open Xunit 4 | open Graphoscope 5 | open Graphoscope.Algorithms 6 | 7 | 8 | 9 | [] 10 | let ``BFS simple example on FGraph works correctly`` () = 11 | 12 | let actual = 13 | FGraph.empty 14 | |> FGraph.addElement 0 'A' 1 'B' 0.1 15 | |> FGraph.addElement 0 'A' 2 'C' 0.1 16 | |> FGraph.addElement 1 'B' 2 'C' 0.1 17 | |> FGraph.addElement 2 'C' 0 'A' 0.1 18 | |> FGraph.addElement 2 'C' 3 'D' 0.1 19 | |> FGraph.addElement 3 'D' 3 'D' 0.1 20 | |> BFS.ofFGraph 2 21 | |> List.ofSeq 22 | 23 | // Following is Breadth First Traversal (starting from vertex 2) 24 | // 2 0 3 1 25 | let expected = [(2,'C');(0,'A');(3,'D');(1,'B')] 26 | 27 | Assert.Equal<(int * char) seq>(expected, actual) 28 | 29 | 30 | [] 31 | let ``BFS.ofFGraphBy returns correct result`` () = 32 | 33 | let actual = BFS.ofFGraphBy 1 (fun nk _ _ -> nk < 5) ReferenceObjects.fGraph1 |> Seq.toList 34 | let expected = [(1, ""); (2, ""); (3, ""); (4, "")] 35 | 36 | Assert.Equal<(int * string) list>(expected, actual) 37 | 38 | 39 | [] 40 | let ``BFS.ofFGraphWithDepth returns correct result`` () = 41 | 42 | let actual = BFS.ofFGraphWithDepth 1 3 ReferenceObjects.fGraph1 |> Seq.toList 43 | let expected = [(1, ""); (2, ""); (7, ""); (3, ""); (4, "")] 44 | 45 | Assert.Equal<(int * string) list>(expected, actual) 46 | 47 | 48 | [] 49 | let ``BFS.ofFGrapWithDepthBy returns correct result`` () = 50 | 51 | let actual1 = BFS.ofFGraphWithDepthBy 1 3 (fun _ _ _ -> true) ReferenceObjects.fGraph1 |> Seq.toList 52 | let actual2 = BFS.ofFGraphWithDepthBy 1 System.Int32.MaxValue (fun nk _ _ -> nk < 5) ReferenceObjects.fGraph1 |> Seq.toList 53 | let expected1 = [(1, ""); (2, ""); (7, ""); (3, ""); (4, "")] 54 | let expected2 = [(1, ""); (2, ""); (3, ""); (4, "")] 55 | 56 | Assert.Equal<(int * string) list>(expected1, actual1) 57 | Assert.Equal<(int * string) list>(expected2, actual2) -------------------------------------------------------------------------------- /docs/index.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Getting Started 4 | category: Graphoscope 5 | categoryindex: 1 6 | index: 1 7 | --- 8 | 9 | # Graphoscope 10 | 11 | The Graphoscope project aims to provide a rigorous and performant tool for Network Science. 12 | It is aimed at anyone who works with Graphs/Networks and does not require a strong knowledge of F# to get started. 13 | 14 | # Getting Started 15 | 16 | ## Prerequisites 17 | 18 | To set up a dev environment, we recommend [VSCode](https://code.visualstudio.com/) with the [Ionide](https://marketplace.visualstudio.com/items?itemName=Ionide.Ionide-fsharp) plugin 19 | You will also need [DotNet 6](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) installed. 20 | 21 | The library is designed primarily for use in an fsharp scripting environment using .fsx files. 22 | But it also works well in [notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode). 23 | This [video](https://www.youtube.com/watch?v=1ROKvmcOloo&list=PLdo4fOcmZ0oUFghYOp89baYFBTGxUkC7Z&index=3) has a good walk through of setting your environment. 24 | 25 | # Contributing and copyright 26 | 27 | The project is hosted on [GitHub][gh] where you can [report issues][issues], fork 28 | the project and submit pull requests. If you're adding a new public API, please also 29 | consider adding [samples][docs] that can be turned into a documentation. You might 30 | also want to read the [library design notes][readme] to understand how it works. 31 | 32 | The library is available under the OSI-approved MIT license, which allows modification and 33 | redistribution for both commercial and non-commercial purposes. For more information see the 34 | [License file][license] in the GitHub repository. 35 | 36 | [docs]: https://github.com/fslaborg/Graphoscope/tree/main/docs 37 | [gh]: https://github.com/fslaborg/Graphoscope 38 | [issues]: https://github.com/fslaborg/Graphoscope/issues 39 | [readme]: https://github.com/fslaborg/Graphoscope/blob/main/README.md 40 | [license]: https://github.com/fslaborg/Graphoscope/blob/main/LICENSE 41 | *) -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Graphoscope.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | false 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Always 29 | 30 | 31 | 32 | 33 | 34 | 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | all 37 | 38 | 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | all 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/M_3_Loops.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Loops 4 | category: Measures 5 | categoryindex: 2 6 | index: 3 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpAux.IO, 2.0.0" 15 | #r "nuget: FSharp.Data, 6.2.0" 16 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 17 | 18 | (*** condition: ipynb ***) 19 | #if IPYNB 20 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 21 | #endif // IPYNB 22 | 23 | (** 24 | # Intoduction to Measures using FGraph 25 | Graphoscope provides a comprehensive set of measurement tools designed to analyze, quantify, and interpret the features of graphs. 26 | These measurements offer valuable insights into the topology, connectivity, and dynamics of your networks. 27 | Whether you are exploring social connections, optimizing communication pathways, or studying the spread of diseases, our graph measurement functionalities are here to simplify your analysis and decision-making processes. 28 | ## Creating a graph by reading a complete graph representation as one. 29 | Step 1 is the loading of our [example graph](http://konect.cc/networks/moreno_rhesus/), sourced from [The KONECT Project](http://konect.cc) describing the grooming interactions between rhesus monkeys. 30 | *) 31 | (***hide***) 32 | open Graphoscope 33 | open FSharpAux.IO 34 | open FSharpAux.IO.SchemaReader.Attribute 35 | open FSharp.Data 36 | 37 | let file = __SOURCE_DIRECTORY__ + "/../tests/Graphoscope.Tests/ReferenceGraphs/out.moreno_rhesus_rhesus.txt" 38 | 39 | let monkeyGraphLoop = 40 | CsvFile.Load(file, " ", skipRows = 2, hasHeaders = false).Rows 41 | |> Seq.map (fun row -> 42 | int row[0],int row[0], int row[1],int row[1], float row[2]) 43 | |> FGraph.ofSeq 44 | 45 | (** 46 | ## Loops 47 | The loop count of a graph, also known as the number of self-loops, refers to the number of edges in the graph that connect a node to itself. 48 | While some may consider self-loops as noise or artifacts, in other cases, they offer valuable insights into the system being modeled and form an integral part of the graph's structure. 49 | *) 50 | 51 | let loopCount = Measures.Loop.loopCount monkeyGraphLoop 52 | 53 | (***hide***) 54 | let loop = sprintf "The graph has %i loops"loopCount 55 | (*** include-value: loop ***) 56 | -------------------------------------------------------------------------------- /docs/M_2_NetworkDensity.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Network Density 4 | category: Measures 5 | categoryindex: 2 6 | index: 2 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpAux.IO, 2.0.0" 15 | #r "nuget: FSharp.Data, 6.2.0" 16 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 17 | 18 | (*** condition: ipynb ***) 19 | #if IPYNB 20 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 21 | #endif // IPYNB 22 | 23 | (** 24 | # Intoduction to Measures using FGraph 25 | Graphoscope provides a comprehensive set of measurement tools designed to analyze, quantify, and interpret the features of graphs. 26 | These measurements offer valuable insights into the topology, connectivity, and dynamics of your networks. 27 | Whether you are exploring social connections, optimizing communication pathways, or studying the spread of diseases, our graph measurement functionalities are here to simplify your analysis and decision-making processes. 28 | ## Creating a graph by reading a complete graph representation as one. 29 | Step 1 is the loading of our [example graph](http://konect.cc/networks/moreno_rhesus/), sourced from [The KONECT Project](http://konect.cc) describing the grooming interactions between rhesus monkeys. 30 | *) 31 | (***hide***) 32 | open Graphoscope 33 | open FSharpAux.IO 34 | open FSharpAux.IO.SchemaReader.Attribute 35 | open FSharp.Data 36 | 37 | let file = __SOURCE_DIRECTORY__ + "/../tests/Graphoscope.Tests/ReferenceGraphs/out.moreno_rhesus_rhesus.txt" 38 | 39 | let monkeyGraphDens = 40 | CsvFile.Load(file, " ", skipRows = 2, hasHeaders = false).Rows 41 | |> Seq.map (fun row -> 42 | int row[0],int row[0], int row[1],int row[1], float row[2]) 43 | |> FGraph.ofSeq 44 | 45 | (** 46 | 47 | ## NetworkDensity 48 | Network density measures the proportion of connections or edges present in a network relative to the total possible number of connections. 49 | It quantifies the level of interconnectedness between nodes in the network and carries several key implications for the Connectivity of the graph. 50 | *) 51 | let networkDensity = Measures.GraphDensity.compute monkeyGraphDens 52 | 53 | (***hide***) 54 | let density = sprintf "The network density of the monkey graph is %f" networkDensity 55 | (*** include-value: density ***) 56 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/BellmanFord.fs: -------------------------------------------------------------------------------- 1 | module BellmanFord 2 | 3 | open Xunit 4 | open Graphoscope 5 | open FSharpAux 6 | 7 | let bfTest = 8 | FGraph.empty 9 | |> FGraph.addElement 0 "Node A" 1 "Node B" -1. 10 | |> FGraph.addElement 0 "Node A" 2 "Node C" 4. 11 | |> FGraph.addElement 1 "Node B" 2 "Node C" 3. 12 | |> FGraph.addElement 1 "Node B" 3 "Node D" 2. 13 | |> FGraph.addElement 1 "Node B" 4 "Node E" 2. 14 | |> FGraph.addElement 3 "Node D" 2 "Node C" 5. 15 | |> FGraph.addElement 3 "Node D" 1 "Node B" 1. 16 | |> FGraph.addElement 4 "Node E" 3 "Node D" -3. 17 | 18 | 19 | [] 20 | let ``BellmanFord simple example on FGraph works correctly`` () = 21 | 22 | let actual = Algorithms.BellmanFord.ofFGraph 0 bfTest 23 | 24 | // Vertex Distance from Source 25 | // 0 0 26 | // 1 -1 27 | // 2 2 28 | // 3 -2 29 | // 4 1 30 | 31 | let expected = [(0, 0.0); (1, -1.); (2, 2.0); (3, -2.0); (4, 1.0)] |> Set.ofList 32 | 33 | Assert.Equal>(expected, actual |> Dictionary.toSeq |> Set.ofSeq) 34 | 35 | 36 | 37 | [] 38 | let ``BellmanFord without cycle`` () = 39 | 40 | let bf = Algorithms.BellmanFord.ofFGraph 0 bfTest 41 | let actual = Algorithms.BellmanFord.hasNegativeCycles bfTest bf 42 | 43 | let expected = false 44 | 45 | Assert.Equal(expected, actual) 46 | 47 | 48 | [] 49 | let ``BellmanFord with cycle`` () = 50 | 51 | let bfTestWithCycle = 52 | FGraph.empty 53 | |> FGraph.addElement 0 "Node A" 1 "Node B" -1. 54 | |> FGraph.addElement 0 "Node A" 2 "Node C" 4. 55 | |> FGraph.addElement 1 "Node B" 2 "Node C" 3. 56 | |> FGraph.addElement 1 "Node B" 3 "Node D" 2. 57 | |> FGraph.addElement 1 "Node B" 4 "Node E" 2. 58 | |> FGraph.addElement 3 "Node D" 2 "Node C" 5. 59 | |> FGraph.addElement 3 "Node D" 1 "Node B" 1. 60 | |> FGraph.addElement 4 "Node E" 3 "Node D" -3. 61 | |> FGraph.addElement 3 "Node D" 0 "Node A" -1. 62 | |> FGraph.addElement 0 "Node A" 4 "Node E" -1. 63 | 64 | let bf = Algorithms.BellmanFord.ofFGraph 0 bfTestWithCycle 65 | let actual = Algorithms.BellmanFord.hasNegativeCycles bfTestWithCycle bf 66 | 67 | let expected = true 68 | 69 | Assert.Equal(expected, actual) 70 | 71 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/FloydWarshall.fs: -------------------------------------------------------------------------------- 1 | module FloydWarshall 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open System.IO 7 | open FSharpAux 8 | 9 | let edges = 10 | [|(0, 3, 1.0); (0, 5, 1.0); (0, 6, 1.0); (0, 7, 1.0); (0, 8, 1.0); 11 | (0, 9, 1.0); (1, 0, 1.0); (1, 4, 1.0); (1, 9, 1.0); (2, 1, 1.0); 12 | (2, 4, 1.0); (2, 6, 1.0); (3, 1, 1.0); (3, 2, 1.0); (3, 4, 1.0); 13 | (3, 5, 1.0); (3, 6, 1.0); (3, 7, 1.0); (4, 0, 1.0); (4, 1, 1.0); 14 | (4, 3, 1.0); (4, 6, 1.0); (4, 7, 1.0); (4, 8, 1.0); (5, 1, 1.0); 15 | (5, 2, 1.0); (5, 4, 1.0); (5, 6, 1.0); (6, 2, 1.0); (6, 3, 1.0); 16 | (6, 4, 1.0); (6, 5, 1.0); (6, 8, 1.0); (7, 0, 1.0); (7, 3, 1.0); 17 | (7, 5, 1.0); (7, 8, 1.0); (7, 9, 1.0); (8, 2, 1.0); (8, 3, 1.0); 18 | (8, 4, 1.0); (8, 5, 1.0); (8, 9, 1.0); (9, 0, 1.0); (9, 3, 1.0); 19 | (9, 4, 1.0); (9, 5, 1.0)|] 20 | 21 | let expected = 22 | [| 23 | [|0.0; 2.0; 2.0; 1.0; 2.0; 1.0; 1.0; 1.0; 1.0; 1.0|] 24 | [|1.0; 0.0; 3.0; 2.0; 1.0; 2.0; 2.0; 2.0; 2.0; 1.0|] 25 | [|2.0; 1.0; 0.0; 2.0; 1.0; 2.0; 1.0; 2.0; 2.0; 2.0|] 26 | [|2.0; 1.0; 1.0; 0.0; 1.0; 1.0; 1.0; 1.0; 2.0; 2.0|] 27 | [|1.0; 1.0; 2.0; 1.0; 0.0; 2.0; 1.0; 1.0; 1.0; 2.0|] 28 | [|2.0; 1.0; 1.0; 2.0; 1.0; 0.0; 1.0; 2.0; 2.0; 2.0|] 29 | [|2.0; 2.0; 1.0; 1.0; 1.0; 1.0; 0.0; 2.0; 1.0; 2.0|] 30 | [|1.0; 2.0; 2.0; 1.0; 2.0; 1.0; 2.0; 0.0; 1.0; 1.0|] 31 | [|2.0; 2.0; 1.0; 1.0; 1.0; 1.0; 2.0; 2.0; 0.0; 1.0|] 32 | [|1.0; 2.0; 2.0; 1.0; 1.0; 1.0; 2.0; 2.0; 2.0; 0.0|] 33 | |] 34 | 35 | [] 36 | let ``All Pairs Floyd Warshall for DiGraph works correctly`` () = 37 | 38 | let actual = 39 | DiGraph.createFromNodes (Array.init 10 (fun i -> i, i)) 40 | |> DiGraph.addEdges edges 41 | |> DiGraph.toAdjacencyMatrix id 42 | |> Algorithms.FloydWarshall.fromJaggedArray 43 | 44 | Assert.Equal(expected, actual) 45 | 46 | [] 47 | let ``All Pairs Floyd Warshall for FGraph works correctly`` () = 48 | 49 | let actual = 50 | edges 51 | |> Seq.map (fun (s, t, w) -> s,s,t,t,w) 52 | |> FGraph.ofSeq 53 | |> FGraph.toArray2D (id) 54 | |> Algorithms.FloydWarshall.fromArray2D 55 | printfn "%A" actual 56 | 57 | let expected = Array2D.init 10 10 (fun n m -> expected.[n].[m]) 58 | Assert.Equal(expected, actual) -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Loop.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | 3 | open Graphoscope 4 | open FSharpAux 5 | 6 | type Loop() = 7 | 8 | /// 9 | /// Get the amount of self loops. 10 | /// 11 | /// The graph to be analysed 12 | /// An int of loop count 13 | static member loopCountFGraph (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) = 14 | [| 15 | for values in graph do 16 | values.Key, 17 | values.Value|>fun (p,n,s) -> p 18 | |] 19 | |> Array.sumBy(fun (nk,v) -> 20 | v.Keys|>Seq.countIf (fun x -> x=nk) 21 | ) 22 | 23 | /// 24 | /// Get the amount of self loops. 25 | /// 26 | /// The graph to be analysed 27 | /// An int of loop count 28 | static member loopCountAdjGraph (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 29 | [| 30 | for values in graph do 31 | values.Key, 32 | values.Value|>fun (n,s) -> s 33 | |] 34 | |> Array.sumBy(fun (nk,v) -> 35 | v.Keys|>Seq.countIf (fun x -> x=nk) 36 | ) 37 | 38 | /// 39 | /// Get the amount of self loops. 40 | /// 41 | /// The graph to be analysed 42 | /// An int of loop count 43 | static member loopCountOfDiGraph (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 44 | graph.InEdges 45 | |> ResizeArray.mapi(fun i x -> 46 | match x |> ResizeArray.tryFind(fun (t,_) -> t = i) with 47 | | Some _ -> 1 48 | | None -> 0 49 | ) 50 | |> Seq.sum 51 | 52 | /// 53 | /// Get the amount of self loops. 54 | /// 55 | /// The graph to be analysed 56 | /// An int of the mean degree 57 | static member loopCount (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) = 58 | Loop.loopCountFGraph graph 59 | 60 | /// 61 | /// Get the amount of self loops. 62 | /// 63 | /// The graph to be analysed 64 | /// An int of the mean degree 65 | static member loopCount (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 66 | Loop.loopCountAdjGraph graph 67 | 68 | /// 69 | /// Get the amount of self loops. 70 | /// 71 | /// The graph to be analysed 72 | /// An int of the mean degree 73 | static member loopCount (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 74 | Loop.loopCountOfDiGraph graph -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Size.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | open Graphoscope 3 | 4 | 5 | type Size() = 6 | /// 7 | /// Gets the total number of nodes of the graph 8 | /// 9 | /// The graph to be analysed 10 | /// A float of the total nodes 11 | static member sizeOfUndirected (graph: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) = 12 | UndirectedGraph.countNodes graph 13 | 14 | /// 15 | /// Gets the total number of nodes of the graph 16 | /// 17 | /// The graph to be analysed 18 | /// A float of the total nodes 19 | static member sizeOfDiGraph (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 20 | DiGraph.countNodes graph 21 | 22 | /// 23 | /// Gets the total number of nodes of the graph 24 | /// 25 | /// The graph to be analysed 26 | /// A float of the total nodes 27 | static member sizeOfFGraph (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) = 28 | FGraph.countNodes graph 29 | 30 | /// 31 | /// Gets the total number of nodes of the graph 32 | /// 33 | /// The graph to be analysed 34 | /// A float of the total nodes 35 | static member sizeOfAdjGraph (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 36 | AdjGraph.countNodes graph 37 | 38 | /// 39 | /// Gets the total number of nodes of the graph 40 | /// 41 | /// The graph to be analysed 42 | /// A float of the total nodes 43 | static member compute (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) = 44 | Size.sizeOfFGraph graph 45 | 46 | /// 47 | /// Gets the total number of nodes of the graph 48 | /// 49 | /// The graph to be analysed 50 | /// A float of the total nodes 51 | static member compute (graph : UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) = 52 | Size.sizeOfUndirected graph 53 | 54 | /// 55 | /// Gets the total number of nodes of the graph 56 | /// 57 | /// The graph to be analysed 58 | /// A float of the total nodes 59 | static member compute (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 60 | Size.sizeOfAdjGraph graph 61 | 62 | /// 63 | /// Gets the total number of nodes of the graph 64 | /// 65 | /// The graph to be analysed 66 | /// A float of the total nodes 67 | static member compute (graph :DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 68 | Size.sizeOfDiGraph graph -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Volume.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | open Graphoscope 3 | 4 | type Volume() = 5 | 6 | /// 7 | /// Gets the total number of edges of the graph 8 | /// 9 | /// The graph to be analysed 10 | /// A float of the total edges 11 | static member volumeOfUndirected (graph: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) = 12 | UndirectedGraph.countEdges graph 13 | 14 | /// 15 | /// Gets the total number of edges of the graph 16 | /// 17 | /// The graph to be analysed 18 | /// A float of the total edges 19 | static member volumeOfDiGraph (graph: DiGraph<'NodeKey, _,'EdgeData>) = 20 | DiGraph.countEdges graph 21 | 22 | /// 23 | /// Gets the total number of edges of the graph 24 | /// 25 | /// The graph to be analysed 26 | /// A float of the total edges 27 | static member volumeOfFGraph (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) = 28 | FGraph.countEdges graph 29 | 30 | /// 31 | /// Gets the total number of edges of the graph 32 | /// 33 | /// The graph to be analysed 34 | /// A float of the total edges 35 | static member volumeOfAdjGraph (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 36 | AdjGraph.countEdges graph / 2 37 | 38 | /// 39 | /// Gets the total number of edges of the graph 40 | /// 41 | /// The graph to be analysed 42 | /// A float of the total edges 43 | static member compute (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) = 44 | Volume.volumeOfFGraph graph 45 | 46 | /// 47 | /// Gets the total number of edges of the graph 48 | /// 49 | /// The graph to be analysed 50 | /// A float of the total edges 51 | static member compute (graph :UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) = 52 | Volume.volumeOfUndirected graph 53 | 54 | /// 55 | /// Gets the total number of edges of the graph 56 | /// 57 | /// The graph to be analysed 58 | /// A float of the total edges 59 | static member compute (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 60 | Volume.volumeOfAdjGraph graph 61 | 62 | /// 63 | /// Gets the total number of edges of the graph 64 | /// 65 | /// The graph to be analysed 66 | /// A float of the total edges 67 | static member compute (graph :DiGraph<'NodeKey, 'NodeData,'EdgeData>) = 68 | Volume.volumeOfDiGraph graph 69 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/DFS.fs: -------------------------------------------------------------------------------- 1 | module DFS 2 | 3 | 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.Algorithms 7 | open ReferenceObjects 8 | 9 | 10 | 11 | [] 12 | let ``DFS simple example on FGraph works correctly`` () = 13 | 14 | let actual = 15 | FGraph.empty 16 | |> FGraph.addElement 1 "Node 1" 2 "Node 2" 0.1 17 | |> FGraph.addElement 1 "Node 1" 3 "Node 3" 0.1 18 | |> FGraph.addElement 2 "Node 2" 4 "Node 4" 0.1 19 | |> FGraph.addElement 2 "Node 2" 5 "Node 5" 0.1 20 | |> FGraph.addElement 3 "Node 3" 6 "Node 6" 0.1 21 | |> FGraph.addElement 5 "Node 5" 7 "Node 7" 0.1 22 | |> DFS.ofFGraph 1 23 | 24 | 25 | // DFS Traversal Result (starting node 1): 26 | let expected = [( 1, "Node 1"); ( 3, "Node 3"); ( 6, "Node 6"); ( 2, "Node 2"); 27 | ( 5, "Node 5"); ( 7, "Node 7"); ( 4, "Node 4");] 28 | 29 | Assert.Equal>(expected, actual) 30 | 31 | 32 | [] 33 | let ``DFS.ofFGraphBy returns correct result`` () = 34 | 35 | let actual = DFS.ofFGraphBy 1 (fun nk _ _ -> nk <> 4) fGraph1 |> Seq.toList 36 | let expected = [(1, ""); (7, ""); (2, ""); (3, "")] 37 | 38 | Assert.Equal<(int * string) list>(expected, actual) 39 | 40 | 41 | [] 42 | let ``DFS.ofFGraphWithDepth returns correct result`` () = 43 | 44 | let actual = DFS.ofFGraphWithDepth 1 3 fGraph1 |> Seq.toList 45 | let expected = [(1, ""); (7, ""); (2, ""); (3, ""); (4, "")] 46 | 47 | Assert.Equal<(int * string) list>(expected, actual) 48 | 49 | 50 | [] 51 | let ``DFS.ofFGraphWithDepthBy returns correct result`` () = 52 | 53 | let actual1 = DFS.ofFGraphWithDepthBy 1 3 (fun _ _ _ -> true) fGraph1 |> Seq.toList 54 | let actual2 = DFS.ofFGraphWithDepthBy 1 System.Int32.MaxValue (fun nk _ _ -> nk <> 4) fGraph1 |> Seq.toList 55 | let expected1 = [(1, ""); (7, ""); (2, ""); (3, ""); (4, "")] 56 | let expected2 = [(1, ""); (7, ""); (2, ""); (3, "")] 57 | 58 | Assert.Equal<(int * string) list>(expected1, actual1) 59 | Assert.Equal<(int * string) list>(expected2, actual2) 60 | 61 | 62 | [] 63 | let ``DFS works on DiGraph`` () = 64 | 65 | let actual = 66 | DiGraph.empty 67 | |> DiGraph.addNode 1 1 68 | |> DiGraph.addNode 2 2 69 | |> DiGraph.addNode 3 3 70 | |> DiGraph.addNode 4 4 71 | |> DiGraph.addNode 5 5 72 | |> DiGraph.addNode 6 6 73 | |> DiGraph.addNode 7 7 74 | |> DiGraph.addEdge (1, 2, 1.0) 75 | |> DiGraph.addEdge (1, 3, 1.0) 76 | |> DiGraph.addEdge (2, 4, 1.0) 77 | |> DiGraph.addEdge (2, 5, 1.0) 78 | |> DiGraph.addEdge (3, 6, 1.0) 79 | |> DiGraph.addEdge (5, 7, 1.0) 80 | |> DFS.ofDiGraph 1 81 | 82 | 83 | // DFS Traversal Result (starting node 1): 84 | let expected = [( 1, 1); ( 3, 3); ( 6, 6); ( 2, 2); 85 | ( 5, 5); ( 7, 7); ( 4, 4);] 86 | 87 | Assert.Equal>(expected, actual) 88 | 89 | -------------------------------------------------------------------------------- /src/Graphoscope/RandomModels/StarGraph.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.RandomModels 2 | 3 | 4 | open Graphoscope 5 | 6 | type StarGraph() = 7 | 8 | 9 | 10 | /// 11 | /// Returns a StarGraph 12 | /// 13 | /// Specifies the number of nodes 14 | /// Function to create a NodeKey of an integer 15 | /// Function to create NodeData of NodeKeys 16 | /// Function to create EdgeData of NodeKeys 17 | /// A Sequence to create a StarGraph 18 | /// 19 | static member initStarGraphSeq (n: int) (nodeIDFunction:int -> 'NodeKey) (nodeDataFunction:'NodeKey -> 'NodeData) (edgeDataFunction:'NodeKey ->'NodeKey ->'EdgeData) :seq<'NodeKey * 'NodeData * 'NodeKey * 'NodeData * 'EdgeData> = 20 | 21 | let allNodes = 22 | Seq.init n (fun x -> nodeIDFunction x) 23 | 24 | let keyNodeID = 25 | Seq.head allNodes 26 | 27 | let keyNodeData = 28 | nodeDataFunction keyNodeID 29 | 30 | allNodes 31 | |> Seq.tail 32 | |> Seq.map(fun nk2 -> 33 | keyNodeID, 34 | keyNodeData, 35 | nk2, 36 | nodeDataFunction nk2, 37 | edgeDataFunction keyNodeID nk2 38 | ) 39 | 40 | /// 41 | /// Returns a StarGraph 42 | /// 43 | /// Specifies the number of nodes 44 | /// Function to create a NodeKey of an integer 45 | /// Function to create NodeData of NodeKeys 46 | /// Function to create EdgeData of NodeKeys 47 | /// A StarFGraph 48 | static member initStarFGraph (n: int) (nodeIDFunction:int -> 'NodeKey) (nodeDataFunction:'NodeKey -> 'NodeData) (edgeDataFunction:'NodeKey ->'NodeKey ->'EdgeData) :FGraph<'NodeKey,'NodeData,'EdgeData> = 49 | let graphSeq = StarGraph.initStarGraphSeq n nodeIDFunction nodeDataFunction edgeDataFunction 50 | FGraph.ofSeq graphSeq 51 | 52 | /// 53 | /// Returns a StarGraph 54 | /// 55 | /// Specifies the number of nodes 56 | /// Function to create a NodeKey of an integer 57 | /// Function to create NodeData of NodeKeys 58 | /// Function to create EdgeData of NodeKeys 59 | /// A StarDiGraph 60 | static member initStarDiGraph (n: int) (nodeIDFunction:int -> 'NodeKey) (nodeDataFunction:'NodeKey -> 'NodeData) (edgeDataFunction:'NodeKey ->'NodeKey ->'EdgeData) :DiGraph<'NodeKey,'NodeData,'EdgeData> = 61 | let graphSeq = StarGraph.initStarGraphSeq n nodeIDFunction nodeDataFunction edgeDataFunction 62 | DiGraph.ofSeq graphSeq 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/Graphoscope/Algorithms/BellmanFord.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Algorithms 2 | 3 | open Graphoscope 4 | open System.Collections.Generic 5 | 6 | module private BF = 7 | 8 | let rec internal loopEdges (snk:'NodeKey) (adj: byref>) 9 | (distances:Dictionary<'NodeKey, float>) = 10 | match adj.MoveNext() with 11 | | true -> 12 | let weight = adj.Current.Value 13 | if (distances[snk] <> System.Double.MaxValue 14 | && distances[snk] + weight < distances[adj.Current.Key]) then 15 | true 16 | else 17 | loopEdges snk (&adj) distances 18 | | false -> false 19 | 20 | 21 | 22 | /// 23 | /// Computes BellmanFord shortest path 24 | /// 25 | type BellmanFord() = 26 | 27 | 28 | 29 | static member ofFGraph (starting: 'NodeKey) (graph : FGraph<'NodeKey, 'NodeData, float>) = 30 | let nodesCount = graph.Count 31 | let distances = new Dictionary<'NodeKey, float>() 32 | 33 | // Step 1: Initialize distances from src to all 34 | // other vertices as INFINITE 35 | // Initialize distances to infinity for all nodes except the starting node 36 | for nodeKey in graph.Keys do 37 | if nodeKey = starting then 38 | distances.[nodeKey] <- 0 39 | else 40 | distances.[nodeKey] <- System.Double.MaxValue 41 | 42 | // Step 2: Relax all edges |V| - 1 times. A simple 43 | // shortest path from src to any other vertex can 44 | // have at-most |V| - 1 edges 45 | for nkv in graph do 46 | let (_,_,p) = nkv.Value 47 | for ekv in p do 48 | if (distances[nkv.Key] <> System.Double.MaxValue 49 | && distances[nkv.Key] + ekv.Value < distances[ekv.Key]) then 50 | distances[ekv.Key] <- distances[nkv.Key] + ekv.Value 51 | 52 | distances 53 | 54 | 55 | static member hasNegativeCycles (graph : FGraph<'NodeKey, 'NodeData, float>) (distances:Dictionary<'NodeKey, float>) = 56 | 57 | // // Step 3: check for negative-weight cycles. The 58 | // // above step guarantees shortest distances if graph 59 | // // doesn't contain negative weight cycle. If we get 60 | // // a shorter path, then there is a cycle. 61 | let mutable enGraph = graph.GetEnumerator() 62 | let rec loopNodes () = //(enGraph: byref>>) 63 | //(distances:Dictionary<'NodeKey, float>) = 64 | match enGraph.MoveNext() with 65 | | false -> false 66 | | true -> 67 | let nodeKey = enGraph.Current.Key 68 | let (_,_,p) = enGraph.Current.Value 69 | let mutable enP = p.GetEnumerator() 70 | match (BF.loopEdges nodeKey (&enP) distances) with 71 | | true -> true 72 | | false -> loopNodes () 73 | 74 | loopNodes() 75 | 76 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/MatchingIndex.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | open Graphoscope 3 | 4 | type MatchingIndex() = 5 | 6 | /// 7 | /// Get the matching index between two nodes in a FGraph. 8 | /// 9 | /// The NodeKey of one of the nodes 10 | /// The NodeKey for the other node 11 | /// The FGraph to be analysed 12 | /// A float of the matching index between two nodes 13 | static member betweenFGraphNodes (graph : FGraph<'NodeKey,'NodeData,'EdgeData>) (nk1:'NodeKey) (nk2:'NodeKey) = 14 | 15 | let neighbours1 = graph.Item nk1 |> FContext.neighbours |> Set.ofSeq 16 | let neighbours2 = graph.Item nk2 |> FContext.neighbours |> Set.ofSeq 17 | 18 | let distinctCommonNeighbours = Set.intersect neighbours1 neighbours2 |> Set.count |> float 19 | let totalNumberOfNeighbours = Set.union neighbours1 neighbours2 |> Set.count |> float 20 | 21 | distinctCommonNeighbours / totalNumberOfNeighbours 22 | 23 | /// 24 | /// Get the matching index between two nodes in a AdjGraph. 25 | /// 26 | /// The NodeKey of one of the nodes 27 | /// The NodeKey for the other node 28 | /// The FGraph to be analysed 29 | /// A float of the matching index between two nodes 30 | static member betweenAdjGraphNodes (graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>) (nk1:'NodeKey) (nk2:'NodeKey) = 31 | 32 | let neighbours1 = AdjGraph.getNeighbours nk1 graph |> Set.ofSeq 33 | let neighbours2 = AdjGraph.getNeighbours nk2 graph |> Set.ofSeq 34 | 35 | let distinctCommonNeighbours = Set.intersect neighbours1 neighbours2 |> Set.count |> float 36 | let totalNumberOfNeighbours = Set.union neighbours1 neighbours2 |> Set.count |> float 37 | 38 | distinctCommonNeighbours / totalNumberOfNeighbours 39 | 40 | /// 41 | /// Get the matching index between two nodes in a graph. 42 | /// 43 | /// The NodeKey of one of the nodes 44 | /// The NodeKey for the other node 45 | /// The graph to be analysed 46 | /// A float of the matching index between two nodes 47 | static member betweenNodes((graph : FGraph<'NodeKey,'NodeData,'EdgeData>),(nk1:'NodeKey),(nk2:'NodeKey)) = 48 | MatchingIndex.betweenFGraphNodes graph nk1 nk2 49 | 50 | /// 51 | /// Get the matching index between two nodes in a graph. 52 | /// 53 | /// The NodeKey of one of the nodes 54 | /// The NodeKey for the other node 55 | /// The graph to be analysed 56 | /// A float of the matching index between two nodes 57 | static member betweenNodes((graph : AdjGraph<'NodeKey,'NodeData,'EdgeData>),(nk1:'NodeKey),(nk2:'NodeKey)) = 58 | MatchingIndex.betweenAdjGraphNodes graph nk1 nk2 59 | -------------------------------------------------------------------------------- /src/Graphoscope/Algorithms/FloydWarshall.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Algorithms 2 | 3 | open FSharpAux 4 | open Graphoscope 5 | open System.Collections.Generic 6 | 7 | /// 8 | /// Computes all-pairs shortest paths for a given graph using Floyd-Warshall algorithm. 9 | /// 10 | /// The ordered array of nodes and 2D Array of distances where each 11 | /// row and column index corresponds to a node's index in the nodes array. 12 | /// 13 | type FloydWarshall() = 14 | 15 | /// 16 | /// Computes all-pairs shortest paths for using Floyd-Warshall algorithm. 17 | /// 18 | /// The graph for which to compute the shortest path. 19 | /// If there isn't a path between two edges, the distance is set to `infinity`. 20 | /// 21 | /// The ordered array of nodes and 2D Array of distances where each 22 | /// row and column index corresponds to a node's index in the nodes array. 23 | /// 24 | static member fromJaggedArray (graph: float [][]): float [][] = 25 | let count = graph.Length 26 | let arr = 27 | JaggedArray.init count count (fun n m -> 28 | let c = graph[n][m] 29 | if c = 0. && n <> m then 30 | infinity 31 | elif n = m then 32 | 0. 33 | else 34 | c 35 | ) 36 | 37 | for k in 0 .. arr.Length - 1 do 38 | for i in 0 .. arr.Length - 1 do 39 | for j in 0 .. arr.Length - 1 do 40 | let w = arr[i][j] 41 | let newW = arr[i][k] + arr[k][j] 42 | if w > newW then 43 | arr[i][j] <- newW 44 | arr 45 | 46 | /// 47 | /// Computes all-pairs shortest paths for using Floyd-Warshall algorithm. 48 | /// 49 | /// The graph for which to compute the shortest path. 50 | /// If there isn't a path between two edges, the distance is set to `infinity`. 51 | /// 52 | /// The ordered array of nodes and 2D Array of distances where each 53 | /// row and column index corresponds to a node's index in the nodes array. 54 | /// 55 | static member fromArray2D (graph: float [,]) : float [,] = 56 | let n = graph.GetLength(0) 57 | let arr = 58 | Array2D.init n n (fun n m -> 59 | let c = graph[n, m] 60 | if c = 0. && n <> m then 61 | infinity 62 | elif n = m then 63 | 0. 64 | else 65 | c ) 66 | for k in 0 .. n - 1 do 67 | for i in 0 .. n - 1 do 68 | for j in 0 .. n - 1 do 69 | let w = arr[i, j] 70 | let newW = arr[i, k] + arr[k, j] 71 | if w > newW then 72 | arr[i, j] <- newW 73 | arr 74 | 75 | 76 | static member compute() = 42 77 | 78 | 79 | -------------------------------------------------------------------------------- /docs/img/badge-binder.svg: -------------------------------------------------------------------------------- 1 | Run in BinderRun in Binder -------------------------------------------------------------------------------- /docs/A_Dijkstra.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Dijkstra 4 | category: Algorithms 5 | categoryindex: 3 6 | index: 1 7 | --- 8 | *) 9 | 10 | 11 | (*** hide ***) 12 | 13 | (*** condition: prepare ***) 14 | #r "nuget: FSharpAux.Core, 2.0.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 18 | 19 | (*** condition: ipynb ***) 20 | #if IPYNB 21 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 22 | #endif // IPYNB 23 | 24 | (** 25 | # Shortest path between all the vertices using Dijkstra Algorithm on an FGraph. 26 | 27 | Dijkstra algorithm, given by a brilliant Dutch computer scientist and software engineer Dr. Edsger Dijkstra in 1959. 28 | Dijkstra algorithm is a greedy algorithm that solves the single-source shortest path problem for a directed and undirected 29 | graph that has non-negative edge weight. 30 | 31 | Let's start with a directed weighted graph. We will find shortest path between all the vertices using Dijkstra Algorithm. 32 | 33 | *) 34 | 35 | open Graphoscope 36 | 37 | let dwg = 38 | let nodes = [|0,"A";1,"B";2,"C";3,"D";4,"E";5,"F"|] 39 | let edges = [|0,1,7.;0,2,12.;1,2,2.;1,3,9.;2,4,10.;4,3,4.;3,5,1.;4,5,5.|] 40 | FGraph.create(nodes,edges) 41 | 42 | (** 43 | Let´s have a look on the graph: 44 | *) 45 | 46 | (***hide***) 47 | open Cytoscape.NET 48 | let vizGraph = 49 | CyGraph.initEmpty () 50 | |> CyGraph.withElements [ 51 | for (sk,s,tk,t,el) in (FGraph.toSeq dwg) do 52 | let sk, tk = (string sk), (string tk) 53 | yield Elements.node sk [ CyParam.label s ] 54 | yield Elements.node tk [ CyParam.label t ] 55 | yield Elements.edge (sprintf "%s_%s" sk tk) sk tk [ CyParam.label el ] 56 | ] 57 | |> CyGraph.withStyle "node" 58 | [ 59 | CyParam.content =. CyParam.label 60 | CyParam.color "#A00975" 61 | ] 62 | |> CyGraph.withStyle "edge" 63 | [ 64 | CyParam.content =. CyParam.label 65 | CyParam.Curve.style "bezier" 66 | CyParam.Target.Arrow.shape "triangle" 67 | CyParam.Source.Arrow.shape "circle" 68 | CyParam.color "#438AFE" 69 | ] 70 | vizGraph 71 | |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false)) 72 | |> CyGraph.withLayout ( 73 | Layout.initCose (Layout.LayoutOptions.Cose(ComponentSpacing=40)) 74 | ) 75 | |> CyGraph.withSize(800, 400) 76 | |> Cytoscape.NET.HTML.toGraphHTML() 77 | (*** include-it-raw ***) 78 | 79 | (** 80 | And now, let´s compute the shortest paths via Dijkstra : 81 | 82 | *) 83 | let dij = Algorithms.Dijkstra.compute(0,dwg) 84 | 85 | 86 | (** 87 | # Shortest path between all the vertices using Dijkstra Algorithm on DiGraph. 88 | Computation of the shortest paths is also available using the DiGraph structure. 89 | Lets compare them using the same graph as above: 90 | *) 91 | 92 | 93 | let dwgDiGraph = 94 | let nodes = [|0;1;2;3;4;5|]|>Array.map(fun x -> x,x) 95 | let edges = [|0,1,7.;0,2,12.;1,2,2.;1,3,9.;2,4,10.;4,3,4.;3,5,1.;4,5,5.|] 96 | DiGraph.createFromNodes nodes 97 | |> DiGraph.addEdges edges 98 | 99 | 100 | let dijDiGraph = Algorithms.Dijkstra.compute(0,id,dwgDiGraph) 101 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/FGraph.fs: -------------------------------------------------------------------------------- 1 | module FGraph 2 | 3 | open System 4 | open System.Collections.Generic 5 | open Xunit 6 | open Graphoscope 7 | 8 | [] 9 | let ``Can create empty graph and add nodes and edges`` () = 10 | let graph = 11 | FGraph.empty 12 | |> FGraph.addNode 1 'A' 13 | |> FGraph.addNode 2 'B' 14 | |> FGraph.addNode 3 'C' 15 | 16 | |> FGraph.addEdge 1 3 1.0 17 | 18 | Assert.Equal(1.0, (FGraph.countEdges graph)) 19 | Assert.Equal(3.0, (FGraph.countNodes graph)) 20 | Assert.Equal(0.6666666666666666, (Measures.Degree.average graph)) 21 | 22 | 23 | [] 24 | let ``Can create a graph with multiple nodes and edges including loops`` () = 25 | let graph = 26 | FGraph.empty 27 | |> FGraph.addElement 0 0 1 0 true 28 | |> FGraph.addElement 0 0 2 0 true 29 | |> FGraph.addElement 1 0 2 0 true 30 | |> FGraph.addElement 2 0 0 0 true 31 | |> FGraph.addElement 2 0 3 0 true 32 | // Tests loops where node exists 33 | |> FGraph.addElement 3 0 3 0 true 34 | |> FGraph.addElement 5 0 3 0 true 35 | |> FGraph.addElement 6 0 6 0 true 36 | 37 | Assert.Equal(8.0, (FGraph.countEdges graph)) 38 | Assert.Equal(6.0, (FGraph.countNodes graph)) 39 | Assert.Equal(2.6666666666666666, (Measures.Degree.average graph)) 40 | 41 | [] 42 | let ``Can reverse a given FGraph's Edges`` () = 43 | let originalGraph = 44 | FGraph.empty 45 | |> FGraph.addElement 0 0 1 0 true 46 | |> FGraph.addElement 0 0 2 0 true 47 | |> FGraph.addElement 1 0 2 0 true 48 | |> FGraph.addElement 2 0 0 0 true 49 | |> FGraph.addElement 2 0 3 0 true 50 | 51 | let originalGraphManuallyReversed = 52 | FGraph.empty 53 | |> FGraph.addElement 1 0 0 0 true 54 | |> FGraph.addElement 2 0 0 0 true 55 | |> FGraph.addElement 2 0 1 0 true 56 | |> FGraph.addElement 0 0 2 0 true 57 | |> FGraph.addElement 3 0 2 0 true 58 | 59 | let revGraph = FGraph.reverseEdges originalGraph 60 | 61 | let manRevGraphKeys = originalGraphManuallyReversed.Keys |> Seq.sort 62 | let revGraphKeys = revGraph.Keys |> Seq.sort 63 | //let manRevGraphVals = originalGraphManuallyReversed.Values |> Seq.map (fun (n1,e,n2) -> List.ofSeq n1.Keys, e, Seq.toList n2.Keys) 64 | //let revGraphVals = revGraph.Values |> Seq.map (fun (n1,e,n2) -> Seq.toList n1.Keys, e, List.ofSeq n2.Keys) 65 | let manRevGraphVals = originalGraphManuallyReversed.Values |> Seq.map (fun (n1,e,n2) -> List.ofSeq n1.Keys, e, Seq.toList n2.Keys) |> Seq.sort 66 | let revGraphVals = revGraph.Values |> Seq.map (fun (n1,e,n2) -> Seq.toList n1.Keys, e, List.ofSeq n2.Keys) |> Seq.sort 67 | 68 | Assert.Equal(manRevGraphKeys, revGraphKeys) 69 | Assert.Equal(manRevGraphVals, revGraphVals) 70 | Assert.Equal(5.0, (FGraph.countEdges revGraph)) 71 | Assert.Equal(4.0, (FGraph.countNodes revGraph)) 72 | 73 | [] 74 | let ``tryFindEdge returns result correctly`` () = 75 | 76 | let g = FGraph.empty 77 | 78 | FGraph.addNode 1 "nd1" g |> ignore 79 | FGraph.addNode 2 "nd2" g |> ignore 80 | FGraph.addEdge 1 2 "1-2" g |> ignore 81 | 82 | Assert.True(FGraph.tryFindEdge 1 2 g |> Option.isSome) 83 | Assert.True(FGraph.tryFindEdge 1 2 g = (Some (1,2,"1-2"))) 84 | Assert.True(FGraph.tryFindEdge 2 1 g |> Option.isNone) -------------------------------------------------------------------------------- /docs/A_Component.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Components 4 | category: Algorithms 5 | categoryindex: 3 6 | index: 4 7 | --- 8 | *) 9 | 10 | 11 | (*** hide ***) 12 | 13 | (*** condition: prepare ***) 14 | #r "nuget: FSharpAux.Core, 2.0.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 18 | 19 | (*** condition: ipynb ***) 20 | #if IPYNB 21 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 22 | #endif // IPYNB 23 | 24 | (** 25 | # Graph components 26 | In graph theory, a graph component refers to a subset of vertices in a graph, where each vertex is connected to every other vertex in the subset through a path of edges. 27 | Let´s open an example graph and show the community detection per color: 28 | *) 29 | open Graphoscope 30 | open Cytoscape.NET 31 | 32 | let componentExampleGraph = 33 | let edgeSeq = 34 | seq{ 35 | 0,1,1 36 | 0,2,1 37 | 0,3,1 38 | 4,5,1 39 | 4,6,1 40 | 4,7,1 41 | 8,9,1 42 | 6,10,1 43 | 8,11,1 44 | } 45 | |> Seq.map(fun (s,t,w) -> 46 | s,s,t,t,(float w) 47 | ) 48 | AdjGraph.ofSeq edgeSeq 49 | 50 | (** 51 | This graph is seperated into 3 distinct components. 52 | Next we use a pre-generated color-pallet and Cytoscape.NET to visualise the graph and its components: 53 | *) 54 | 55 | let colors = 56 | [ 57 | "#3F51B5" 58 | "#3F51B5" 59 | "#3F51B5" 60 | "#3F51B5" 61 | "#FFC107" 62 | "#FFC107" 63 | "#FFC107" 64 | "#FFC107" 65 | "#FFEB3B" 66 | "#FFEB3B" 67 | "#FFEB3B" 68 | "#FFEB3B" 69 | ] 70 | 71 | let renderCyGraph (nodeLabelF) (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 72 | 73 | CyGraph.initEmpty () 74 | |> CyGraph.withElements [ 75 | for (sk,s,tk,t,el) in (AdjGraph.toSeq graph) do 76 | let sk, tk = (string sk), (string tk) 77 | yield Elements.node sk (nodeLabelF s ) 78 | yield Elements.node tk (nodeLabelF t ) 79 | yield Elements.edge (sprintf "%s_%s" sk tk) sk tk [ ] 80 | ] 81 | |> CyGraph.withStyle "node" 82 | [ 83 | CyParam.color "black" 84 | CyParam.label =. CyParam.label 85 | CyParam.Text.Align.center 86 | CyParam.Text.Outline.width 0.5 87 | CyParam.Background.color =. CyParam.color 88 | CyParam.weight 100 89 | ] 90 | 91 | |> CyGraph.withLayout ( 92 | Layout.initBreadthfirst(Layout.LayoutOptions.Generic()) 93 | ) 94 | |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false)) 95 | |> CyGraph.withSize(800, 400) 96 | |> Cytoscape.NET.HTML.toGraphHTML() 97 | 98 | 99 | renderCyGraph (fun x -> [CyParam.label x;CyParam.color colors.[x]]) componentExampleGraph 100 | (*** include-it-raw ***) 101 | 102 | 103 | (** 104 | We can use Algorithms.Components to seperate the components from each other, where each components gets its own subgraph: 105 | *) 106 | 107 | let components = 108 | Algorithms.Components.getGraphComponentsOfAdjGraph componentExampleGraph 109 | 110 | 111 | renderCyGraph (fun (x) -> [CyParam.label x;CyParam.color colors.[x]]) (components|>Seq.head) 112 | (*** include-it-raw ***) -------------------------------------------------------------------------------- /src/Graphoscope/Graphoscope.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | true 5 | 6 | 7 | FsLab open source contributors 8 | A pragmatic approach to network science. 9 | A pragmatic approach to network science. 10 | MIT 11 | https://fslab.org/Graphoscope 12 | network graph fsharp csharp dotnet 13 | https://github.com/fslaborg/Graphoscope 14 | git 15 | https://github.com/fslaborg/Graphoscope/blob/main/LICENSE 16 | https://github.com/fslaborg/Graphoscope/blob/main/RELEASE_NOTES.md 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Louvain.fs: -------------------------------------------------------------------------------- 1 | module Louvain 2 | 3 | open Xunit 4 | open Graphoscope 5 | open FSharpAux 6 | 7 | [] 8 | let ``Louvain UndirectedGraph works correctly on `KarateClub` `` () = 9 | // Adapted from Networkx with 1-indexed node keys. 10 | // https://github.com/networkx/networkx/blob/1c5272054f71f9484347f7e4246ae3d5da367f7b/networkx/algorithms/community/tests/test_louvain.py#L27 11 | 12 | let rng = System.Random(42) 13 | let karateFile= __SOURCE_DIRECTORY__ + "/ReferenceGraphs/zachary.txt" 14 | let karateGraph = 15 | System.IO.File.ReadAllLines (karateFile) 16 | |> Array.skip 2 17 | |> Array.map(fun x -> 18 | let cols = x.Split " " 19 | int cols[0], int cols[1], 1.0 20 | ) 21 | |> UndirectedGraph.createFromEdges 22 | 23 | let actual = Algorithms.Louvain.louvainPartitions (karateGraph, rng = rng.NextDouble) 24 | let expected = 25 | [| 26 | set [1; 2; 3; 4; 8; 10; 12; 13; 14; 18; 20; 22] 27 | set [5; 6; 7; 11; 17] 28 | set [9; 15; 16; 19; 21; 23; 27; 30; 31; 33; 34] 29 | set [24; 25; 26; 28; 29; 32] 30 | |] 31 | 32 | Assert.Equal(expected, actual |> Array.last) 33 | 34 | [] 35 | let ``Louvain UndirectedGraph works corectly with `string` node`` () = 36 | let rng = System.Random(123) 37 | let t3Edges = 38 | [| 39 | ("a", "b", 1.0) 40 | ("a", "c", 1.0) 41 | ("b", "c", 1.0) 42 | ("b", "d", 1.0) // inter-community edge 43 | ("d", "e", 1.0) 44 | ("d", "f", 1.0) 45 | ("d", "g", 1.0) 46 | ("f", "g", 1.0) 47 | ("f", "e", 1.0) 48 | |] 49 | 50 | let t3 = UndirectedGraph.createFromEdges t3Edges 51 | 52 | let actual = Algorithms.Louvain.louvainCommunities (t3, rng = rng.NextDouble) 53 | let expected = [|set ["a"; "b"; "c"]; set ["d"; "e"; "f"; "g"]|] 54 | 55 | Assert.Equal(expected, actual) 56 | 57 | 58 | [] 59 | let ``Louvain DiGraph works corectly`` () = 60 | let rng = System.Random(123) 61 | 62 | let t1Edges = [| 63 | (0, 2, 1.) 64 | (0, 1, 1.) 65 | (1, 0, 1.) 66 | (2, 1, 1.) 67 | (2, 0, 1.) 68 | (3, 4, 1.) 69 | (4, 3, 1.) 70 | (7, 8, 1.) 71 | (8, 7, 1.) 72 | (9, 10, 1.) 73 | (10, 9, 1.) 74 | |] 75 | 76 | let t1 = 77 | DiGraph.createFromNodes (Array.init 11 (fun i -> i,i)) 78 | |> DiGraph.addEdges t1Edges 79 | let actualT1 = Algorithms.Louvain.louvainCommunities(t1, id, rng = rng.NextDouble) 80 | 81 | let expectedT1 = [|set [0; 1; 2]; set [3; 4]; set [5]; set [6]; set [7; 8]; set [9; 10]|] 82 | 83 | let t2Edges = [| 84 | (1, 2, 1.) 85 | (1, 6, 1.) 86 | (1, 9, 1.) 87 | (2, 3, 1.) 88 | (2, 4, 1.) 89 | (2, 5, 1.) 90 | (3, 4, 1.) 91 | (4, 3, 1.) 92 | (4, 5, 1.) 93 | (5, 4, 1.) 94 | (6, 7, 1.) 95 | (6, 8, 1.) 96 | (9, 10, 1.) 97 | (9, 11, 1.) 98 | (10, 11, 1.) 99 | (11, 10, 1.) 100 | |] 101 | 102 | let t2 = 103 | DiGraph.createFromNodes (Array.init 11 (fun i -> i+1, i+1)) 104 | |> DiGraph.addEdges t2Edges 105 | let actualT2 = Algorithms.Louvain.louvainCommunities(t2, id, rng = rng.NextDouble) 106 | 107 | let expectedT2 = [|set [1; 6; 7; 8]; set [2; 3; 4; 5]; set [9; 10; 11]|] 108 | 109 | Assert.Equal(expectedT1, actualT1) 110 | Assert.Equal(expectedT2, actualT2) 111 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/ClusteringCoefficient.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | open Graphoscope 3 | 4 | type ClusteringCoefficient() = 5 | ///Evaluates the clustering coefficient of the vertex. 6 | static member clusteringCoefficientOfFGraphVertex (context:FContext<'NodeKey, 'NodeData, 'EdgeData>) (g: FGraph<'NodeKey, 'NodeData, 'EdgeData>) : float= 7 | context 8 | |> fun c -> 9 | if FContext.degree c < 2 then 0. 10 | else 11 | let add1IfInSeq acc x set = 12 | if Seq.contains x set then acc + 1 13 | else acc 14 | let neighbours = FContext.neighbours c|>Seq.map fst 15 | let neighbourEdges = 16 | Seq.fold (fun edgeAmount v' -> 17 | (g.Item v' 18 | |> fun x -> 19 | (FContext.predecessors x|>Seq.map fst 20 | |> Seq.fold (fun acc (x) -> add1IfInSeq acc x neighbours) 0)) 21 | + edgeAmount 22 | ) 0 neighbours 23 | let degree = Seq.length neighbours 24 | ((float neighbourEdges) / (float (degree * (degree - 1)))) / 2. 25 | 26 | static member clusteringCoefficientOfFGraph (g: FGraph<'NodeKey, 'NodeData, 'EdgeData>) : float= 27 | g 28 | |> FGraph.mapContexts (fun c -> ClusteringCoefficient.clusteringCoefficientOfFGraphVertex c g) 29 | |> Seq.sumBy snd 30 | 31 | static member clusteringCoefficientOfAdjGraphNode (n:'NodeKey) (g: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) : float= 32 | if (AdjGraph.getDegree g n) < 2 then 33 | 0. 34 | else 35 | let add1IfInSeq acc x set = 36 | if Seq.contains x set then 37 | acc + 1 38 | else 39 | acc 40 | let neighbours = AdjGraph.getNeighbours n g|>Seq.map fst 41 | let neighbourEdges = 42 | Seq.fold (fun edgeAmount v' -> 43 | (AdjGraph.getNeighbours v' g 44 | |> fun (p) -> 45 | (p|>Seq.map fst 46 | |> Seq.fold (fun acc (x) -> add1IfInSeq acc x neighbours) 0)) 47 | + edgeAmount 48 | ) 0 neighbours 49 | let degree = Seq.length neighbours 50 | ((float neighbourEdges) / (float (degree * (degree - 1)))) / 2. 51 | 52 | static member clusteringCoefficientOfAdjGraph (g: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) : float= 53 | g.Keys 54 | |> Seq.map (fun c -> ClusteringCoefficient.clusteringCoefficientOfAdjGraphNode c g) 55 | |> Seq.sum 56 | static member clusteringCoefficientOfDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) : float= 57 | System.NotImplementedException() |> raise 58 | 59 | static member clusteringCoefficientOfUndirectedGraph (g: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) : float= 60 | System.NotImplementedException() |> raise 61 | 62 | static member compute (g: FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 63 | ClusteringCoefficient.clusteringCoefficientOfFGraph g 64 | 65 | static member compute (g: AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) = 66 | ClusteringCoefficient.clusteringCoefficientOfAdjGraph g 67 | 68 | static member compute (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 69 | ClusteringCoefficient.clusteringCoefficientOfDiGraph g 70 | 71 | static member compute (g: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>) = 72 | ClusteringCoefficient.clusteringCoefficientOfUndirectedGraph g 73 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/NetworkDensity.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | 3 | open Graphoscope 4 | open System.Collections.Generic 5 | 6 | /// 7 | /// Computes the graph density 8 | /// 9 | type GraphDensity() = 10 | 11 | /// 12 | /// Computes the graph density of the given graph . 13 | /// 14 | /// The graph for which to compute the graph density. 15 | /// This calculation only works on graphs without self loops 16 | /// 17 | /// The graph density. 18 | /// 19 | static member ofFGraph (graph : FGraph<'NodeKey, 'NodeData, float>) = 20 | let nodesCount = graph.Count|>float 21 | let edgeCount = FGraph.countEdges graph |>float 22 | let potentialConnections = ((nodesCount) * (nodesCount-1.)) 23 | let density = edgeCount / potentialConnections 24 | density 25 | 26 | /// 27 | /// Computes the graph density of the given graph . 28 | /// 29 | /// The graph for which to compute the graph density. 30 | /// This calculation only works on graphs without self loops 31 | /// 32 | /// The graph density. 33 | /// 34 | static member ofAdjGraph (graph : AdjGraph<'NodeKey, 'NodeData, float>) = 35 | let nodesCount = graph.Count|>float 36 | let edgeCount = AdjGraph.countEdges graph |> float |> fun x -> x*2. 37 | let potentialConnections = ((nodesCount) * (nodesCount-1.)) 38 | let density = (edgeCount) / potentialConnections 39 | density 40 | 41 | //TODO 42 | /// 43 | /// Computes the graph density of the given graph . 44 | /// 45 | /// The graph for which to compute the graph density. 46 | /// This calculation only works on graphs without self loops 47 | /// 48 | /// The graph density. 49 | /// 50 | static member ofDiGraph (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 51 | System.NotImplementedException() |> raise 52 | 53 | /// 54 | /// Computes the graph density of the given graph . 55 | /// 56 | /// The graph for which to compute the graph density. 57 | /// This calculation only works on graphs without self loops 58 | /// 59 | /// The graph density. 60 | /// 61 | static member compute (graph : FGraph<'NodeKey, 'NodeData, float>) = 62 | GraphDensity.ofFGraph graph 63 | 64 | /// 65 | /// Computes the graph density of the given graph . 66 | /// 67 | /// The graph for which to compute the graph density. 68 | /// This calculation only works on graphs without self loops 69 | /// 70 | /// The graph density. 71 | /// 72 | static member compute (graph : AdjGraph<'NodeKey, 'NodeData, float>) = 73 | GraphDensity.ofAdjGraph graph 74 | 75 | /// 76 | /// Computes the graph density of the given graph . 77 | /// 78 | /// The graph for which to compute the graph density. 79 | /// This calculation only works on graphs without self loops 80 | /// 81 | /// The graph density. 82 | /// 83 | static member compute (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 84 | GraphDensity.ofDiGraph graph 85 | -------------------------------------------------------------------------------- /docs/A_Louvain.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Louvain-Algorithm 4 | category: Algorithms 5 | categoryindex: 3 6 | index: 3 7 | --- 8 | *) 9 | 10 | 11 | (*** hide ***) 12 | 13 | (*** condition: prepare ***) 14 | #r "nuget: FSharpAux.Core, 2.0.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 18 | 19 | (*** condition: ipynb ***) 20 | #if IPYNB 21 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 22 | #endif // IPYNB 23 | 24 | (** 25 | # The Louvain-Algorithm for Community Detection and Modularity Optimization 26 | The Louvain algorithm is a popular and efficient method for community detection and modularity optimization in complex networks. 27 | Community detection is the task of partitioning a network into groups of nodes, known as communities or clusters, where nodes within a community are densely connected to each other while having fewer connections to nodes in other communities. 28 | Modularity is a measure used to quantify the quality of a given network partition. 29 | Let´s open an example graph and show the community detection per color: 30 | *) 31 | open Graphoscope 32 | open Cytoscape.NET 33 | 34 | let louvainExampleGraph = 35 | let edgeSeq = 36 | seq{ 37 | 0,1,1 38 | 0,3,1 39 | 0,4,1 40 | 1,2,1 41 | 1,4,1 42 | 2,3,1 43 | 3,4,1 44 | 2,5,1 45 | 5,6,1 46 | 5,7,1 47 | 5,8,1 48 | 6,7,1 49 | 7,8,1 50 | } 51 | |> Seq.map(fun (s,t,w) -> 52 | s,s,t,t,(float w) 53 | ) 54 | AdjGraph.ofSeq edgeSeq 55 | (** 56 | This graph is an adaptation of an example graph in the [networksciencebook](http://networksciencebook.com/chapter/9#modularity). 57 | Next we use a pre-generated color-pallet and Cytoscape.NET to visualise the graph and its communites: 58 | *) 59 | 60 | let colors = 61 | [ 62 | "#3F51B5" 63 | "#FFC107" 64 | "#F44336" 65 | "##E91E63" 66 | "#2196F3" 67 | "#00BCD4" 68 | "#C8E6C9" 69 | "#9C27B0" 70 | "#FFEB3B" 71 | 72 | ] 73 | 74 | let renderCyGraph (nodeLabelF) (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 75 | 76 | CyGraph.initEmpty () 77 | |> CyGraph.withElements [ 78 | for (sk,s,tk,t,el) in (AdjGraph.toSeq graph) do 79 | let sk, tk = (string sk), (string tk) 80 | yield Elements.node sk (nodeLabelF s ) 81 | yield Elements.node tk (nodeLabelF t ) 82 | yield Elements.edge (sprintf "%s_%s" sk tk) sk tk [ ] 83 | ] 84 | |> CyGraph.withStyle "node" 85 | [ 86 | CyParam.color "black" 87 | CyParam.label =. CyParam.label 88 | CyParam.Text.Align.center 89 | CyParam.Text.Outline.width 0.5 90 | CyParam.Background.color =. CyParam.color 91 | CyParam.weight 100 92 | ] 93 | 94 | |> CyGraph.withLayout ( 95 | Layout.initBreadthfirst(Layout.LayoutOptions.Generic()) 96 | ) 97 | |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false)) 98 | |> CyGraph.withSize(800, 400) 99 | |> Cytoscape.NET.HTML.toGraphHTML() 100 | 101 | 102 | renderCyGraph (fun x -> [CyParam.label x;CyParam.color colors.[x]]) louvainExampleGraph 103 | (*** include-it-raw ***) 104 | 105 | 106 | (** 107 | Now lets apply our Louvain Algorithm and color the nodes in accordance to their optimised community: 108 | *) 109 | 110 | 111 | let louvainGraph = 112 | Algorithms.Louvain.louvain 0.0001 id louvainExampleGraph 113 | 114 | 115 | renderCyGraph (fun (a,x) -> [CyParam.label x;CyParam.color colors.[x]]) louvainGraph 116 | (*** include-it-raw ***) -------------------------------------------------------------------------------- /docs/M_1_Degree.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Degree 4 | category: Measures 5 | categoryindex: 2 6 | index: 1 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpAux.IO, 2.0.0" 15 | #r "nuget: FSharp.Data, 6.2.0" 16 | #r "nuget: Plotly.NET, 4.1.0" 17 | Plotly.NET.Defaults.DefaultDisplayOptions <- 18 | Plotly.NET.DisplayOptions.init (PlotlyJSReference = Plotly.NET.PlotlyJSReference.NoReference) 19 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 20 | 21 | (*** condition: ipynb ***) 22 | #if IPYNB 23 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 24 | #endif // IPYNB 25 | 26 | (** 27 | # Intoduction to Measures using FGraph 28 | Graphoscope provides a comprehensive set of measurement tools designed to analyze, quantify, and interpret the features of graphs. 29 | These measurements offer valuable insights into the topology, connectivity, and dynamics of your networks. 30 | Whether you are exploring social connections, optimizing communication pathways, or studying the spread of diseases, our graph measurement functionalities are here to simplify your analysis and decision-making processes. 31 | ## Reading a complete graph representation 32 | Step 1 is the loading of our [example graph](http://konect.cc/networks/moreno_rhesus/), sourced from [The KONECT Project](http://konect.cc) describing the grooming interactions between rhesus monkeys. 33 | *) 34 | open Graphoscope 35 | open Plotly.NET 36 | open FSharpAux.IO 37 | open FSharp.Data 38 | open FSharpAux.IO.SchemaReader.Attribute 39 | 40 | (***hide***) 41 | let file = __SOURCE_DIRECTORY__ + "/../tests/Graphoscope.Tests/ReferenceGraphs/out.moreno_rhesus_rhesus.txt" 42 | 43 | let monkeyGraphDeg = 44 | CsvFile.Load(file, " ", skipRows = 2, hasHeaders = false).Rows 45 | |> Seq.map (fun row -> 46 | int row[0],int row[0], int row[1],int row[1], float row[2]) 47 | |> FGraph.ofSeq 48 | 49 | let monkeyGraph2Deg = 50 | 51 | CsvFile.Load(file, " ", skipRows = 2, hasHeaders = false).Rows 52 | |> Seq.map (fun row -> 53 | int row[0],int row[0], int row[1],int row[1], float row[2]) 54 | |> DiGraph.ofSeq 55 | 56 | (** 57 | ## Degree 58 | In graph science, a degree is a fundamental concept that plays a crucial role in understanding the structure and properties of graphs. 59 | The degree of a node in a graph is defined as the number of edges incident to that node, i.e., the number of connections that node has with other nodes in the graph. 60 | The degree is a basic measure that provides valuable information about the topology and connectivity of the graph. 61 | 62 | ### Average Degree 63 | The average degree (also known as the average node degree or average connectivity) of a graph is a measure that indicates, on average, how many connections each node has in the network. 64 | *) 65 | 66 | let averageDegreeMokeyGraph = Measures.Degree.average monkeyGraphDeg 67 | 68 | let averageDegreeMokeyGraph2 = Measures.Degree.average monkeyGraph2Deg 69 | 70 | 71 | (***hide***) 72 | let avD = sprintf "The average degree is %f for FGraph and %f fOr DiGraph" (averageDegreeMokeyGraph) (averageDegreeMokeyGraph2) 73 | (*** include-value: avD ***) 74 | 75 | (** 76 | ### Max Degree 77 | The maximum degree of a graph provides insights into the importance of highly connected nodes (hubs) within the network. 78 | Understanding hubs is crucial for analyzing network resilience, efficiency, and vulnerability 79 | *) 80 | 81 | let maxDregree = Measures.Degree.maximum monkeyGraphDeg 82 | 83 | (***hide***) 84 | let maxD = sprintf "The maximal degree is %i" (maxDregree) 85 | (*** include-value: maxD ***) 86 | 87 | (** 88 | ### Degree Distribution 89 | Degree distribution is an important concept in graph theory and network science that describes the statistical pattern of node degrees in a graph. 90 | It provides valuable insights into the connectivity and structure of networks and plays a crucial role in understanding various aspects of complex systems. 91 | *) 92 | 93 | Measures.Degree.sequence monkeyGraphDeg 94 | |> Chart.Histogram 95 | |> GenericChart.toChartHTML 96 | (***include-it-raw***) 97 | -------------------------------------------------------------------------------- /docs/img/badge-notebook.svg: -------------------------------------------------------------------------------- 1 | Download notebookDownload notebook -------------------------------------------------------------------------------- /src/Graphoscope/RandomModels/Gilbert.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.RandomModels 2 | 3 | open Graphoscope 4 | 5 | // Adaptation of the gilbert random plane networks 6 | // Gilbert, E.N., 1961. Random plane networks. Journal of the society for industrial and applied mathematics, 9(4), pp.533-543. 7 | /// Returns an ArrayAdjacencyGraph that is generated randomly with the given parameters. 8 | /// 9 | /// numberOfNodes indicates the number of vertices the final graph will have. 10 | /// probability represents the probability of an edge between 2 vertices. 11 | type Gilbert() = 12 | 13 | 14 | /// 15 | /// Inits a directed AdjGraph according to the gilbert random plane networks 16 | /// 17 | static member initDirectedAdjGraph (numberOfNodes: int) (probability: float) = 18 | if probability > 1. || probability < 0. then failwithf "The stated probability %F is outside the expected range of 0. to 1." probability 19 | 20 | let rnd = new System.Random() 21 | let g = AdjGraph.empty 22 | 23 | for i=0 to (numberOfNodes-1) do 24 | for ii=0 to (numberOfNodes-1) do 25 | if rnd.NextDouble() < probability then 26 | g |> AdjGraph.addElement i i ii ii probability |> ignore 27 | g 28 | 29 | /// 30 | /// Inits a directed FGraph according to the gilbert random plane networks 31 | /// 32 | static member initDirectedFGraph (numberOfNodes: int) (probability: float) = 33 | if probability > 1. || probability < 0. then failwithf "The stated probability %F is outside the expected range of 0. to 1." probability 34 | 35 | let rnd = new System.Random() 36 | let g : FGraph = FGraph.empty 37 | 38 | for i=0 to (numberOfNodes-1) do 39 | for ii=0 to (numberOfNodes-1) do 40 | if rnd.NextDouble() < probability then 41 | g |> FGraph.addElement i i ii ii probability |> ignore 42 | g 43 | 44 | /// 45 | /// Inits an undirected FGraph according to the gilbert random plane networks 46 | /// 47 | static member initUndirectedFGraph (numberOfNodes: int) (probability: float) = 48 | if probability > 1. || probability < 0. then failwithf "The stated probability %F is outside the expected range of 0. to 1." probability 49 | 50 | let rnd = new System.Random() 51 | let g : FGraph = FGraph.empty 52 | 53 | for i=0 to (numberOfNodes-1) do 54 | for ii=i to (numberOfNodes-1) do 55 | if rnd.NextDouble() < probability then 56 | g |> FGraph.addElement i i ii ii probability |> ignore 57 | g 58 | 59 | /// 60 | /// Inits an UndirectedGraph according to the gilbert random plane networks 61 | /// 62 | static member initUndirectedGraph (rng: System.Random) (numberOfNodes: int) (probability: float) = 63 | if probability > 1. || probability < 0. then 64 | failwithf "The stated probability %F is outside the expected range of 0. to 1." probability 65 | 66 | let g = UndirectedGraph.createFromNodes (Array.init numberOfNodes (fun i -> i, i)) 67 | UndirectedGraph.getNonLoopingPossibleEdges g 68 | |> Seq.iter( fun (o, d) -> 69 | if rng.NextDouble() <= probability then 70 | UndirectedGraph.addEdge (o, d, 1.0) g |> ignore 71 | ) 72 | g 73 | 74 | /// 75 | /// Inits a DiGraph according to the gilbert random plane networks 76 | /// 77 | static member initDiGraph (rng: System.Random) (numberOfNodes: int) (probability: float) = 78 | if probability > 1. || probability < 0. then 79 | failwithf "The stated probability %F is outside the expected range of 0. to 1." probability 80 | 81 | let g = DiGraph.createFromNodes ([|0 .. numberOfNodes - 1|] |> Array.zip [|0 .. numberOfNodes - 1|]) 82 | DiGraph.getNonLoopingPossibleEdges g 83 | |> Seq.iter( fun (o, d) -> 84 | if rng.NextDouble() <= probability then 85 | DiGraph.addEdge (o, d, 1.0) g |> ignore 86 | ) 87 | g 88 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Diameter.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | open Graphoscope 3 | open FSharpAux 4 | 5 | type Diameter() = 6 | 7 | /// 8 | /// Get the diameter of graph calculated by their minimum Eccentricity 9 | /// 10 | /// Result of the FloydWarshall shortest Path calculation of a graph 11 | /// A float of the shortest shortest Paths of a graph 12 | static member ofGraph2D (floydWarshall : float [,]) = 13 | floydWarshall 14 | |> FSharpAux.Array2D.maxBy id 15 | 16 | /// 17 | /// Get the diameter of graph calculated by their minimum Eccentricity 18 | /// 19 | /// Function to get a float edgeweight of the EdgeData 20 | /// The graph to calculate the diameter for 21 | /// A float of the longest shortest Path of a graph 22 | static member ofFGraph (weigthF:'EdgeData->float) (graph:FGraph<'NodeKey,'NodeData,'EdgeData>) = 23 | Eccentricity.ofFGraph weigthF graph 24 | |> Seq.maxBy snd 25 | |> snd 26 | 27 | /// 28 | /// Get the diameter of graph calculated by their minimum Eccentricity 29 | /// 30 | /// Function to get a float edgeweight of the EdgeData 31 | /// The graph to calculate the diameter for 32 | /// A float of the longest shortest Path of a graph 33 | static member ofAdjGraph (weigthF:'EdgeData->float) (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 34 | Eccentricity.ofAdjGraph weigthF graph 35 | |> Seq.maxBy snd 36 | |> snd 37 | 38 | /// 39 | /// Get the diameter of graph calculated by their minimum Eccentricity 40 | /// 41 | /// The graph to calculate the diameter for 42 | /// A float of the longest shortest Path of a graph 43 | static member compute((graph:FGraph<'NodeKey,'NodeData,'EdgeData>)) = 44 | Diameter.ofFGraph (fun x -> 1.) graph 45 | 46 | /// 47 | /// Get the diameter of graph calculated by their minimum Eccentricity 48 | /// 49 | /// The graph to calculate the diameter for 50 | /// A float of the longest shortest Path of a graph 51 | static member compute((graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>)) = 52 | Diameter.ofAdjGraph (fun x -> 1.) graph 53 | 54 | /// 55 | /// Get the diameter of graph calculated by their minimum Eccentricity 56 | /// 57 | /// The graph to calculate the diameter for 58 | /// A float of the longest shortest Path of a graph 59 | static member computeWithEdgeData((graph:FGraph<'NodeKey,'NodeData,float>)) = 60 | Diameter.ofFGraph id graph 61 | 62 | /// 63 | /// Get the diameter of graph calculated by their minimum Eccentricity 64 | /// 65 | /// The graph to calculate the diameter for 66 | /// A float of the longest shortest Path of a graph 67 | static member computeWithEdgeData((graph:AdjGraph<'NodeKey,'NodeData,float>)) = 68 | Diameter.ofAdjGraph id graph 69 | 70 | 71 | /// 72 | /// Get the diameter of graph calculated by their minimum Eccentricity 73 | /// 74 | /// Function to get a float edgeweight of the EdgeData 75 | /// The graph to calculate the diameter for 76 | /// A float of the longest shortest Path of a graph 77 | static member computeWithEdgeDataBy((weigthF:'EdgeData->float),(graph:FGraph<'NodeKey,'NodeData,'EdgeData>)) = 78 | Diameter.ofFGraph weigthF graph 79 | 80 | /// 81 | /// Get the diameter of graph calculated by their minimum Eccentricity 82 | /// 83 | /// Function to get a float edgeweight of the EdgeData 84 | /// The graph to calculate the diameter for 85 | /// A float of the longest shortest Path of a graph 86 | static member computeWithEdgeDataBy((weigthF:'EdgeData->float),(graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>)) = 87 | Diameter.ofAdjGraph weigthF graph -------------------------------------------------------------------------------- /src/Graphoscope/Algorithms/Components.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Algorithms 2 | 3 | open Graphoscope 4 | open System.Collections.Generic 5 | 6 | type Components() = 7 | 8 | static member getComponentOfAdjGraphNode (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) (nodeID:'NodeKey) :AdjGraph<'NodeKey,'NodeData,'EdgeData> = 9 | let nodesInComponent = 10 | Algorithms.BFS.ofAdjGraph nodeID graph 11 | |> Seq.map(fst) 12 | |> Set.ofSeq 13 | graph 14 | |> AdjGraph.toSeq 15 | |> Seq.filter(fun (nk1,nd1,nk2,nd2,w) -> 16 | Set.contains nk1 nodesInComponent && Set.contains nk2 nodesInComponent 17 | ) 18 | |> AdjGraph.ofSeq 19 | 20 | static member getGraphComponentsOfAdjGraph (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) :seq>= 21 | let rec getSubgraph toVisitSet (bfElementSet:Set>) = 22 | if Set.count toVisitSet = 0 then 23 | bfElementSet 24 | else 25 | let node = Set.minElement toVisitSet 26 | let bfElements = 27 | Algorithms.BFS.ofAdjGraph node graph 28 | |> Seq.map(fst) 29 | |> Set.ofSeq 30 | let reducedToVisit = Set.difference toVisitSet bfElements 31 | getSubgraph reducedToVisit (Set.add (bfElements) bfElementSet) 32 | let bfNodes = getSubgraph (Set.ofSeq graph.Keys) Set.empty 33 | bfNodes 34 | |> Seq.map(fun x -> 35 | graph 36 | |> AdjGraph.toSeq 37 | |> Seq.filter(fun (nk1,nd1,nk2,nd2,w) -> 38 | Set.contains nk1 x && Set.contains nk2 x 39 | ) 40 | |> AdjGraph.ofSeq 41 | ) 42 | 43 | 44 | /// DiGraph 45 | 46 | /// 47 | /// Returns true if all nodes in the graph are weakly connected into one component. 48 | /// 49 | /// The graph to analyse 50 | /// Returns true or false 51 | static member isWeakComponentOfDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 52 | if (DFS.ofDiGraphUndirected (g.NodeKeys |> Seq.head) g 53 | |> Seq.length) = g.NodeKeys .Count then true 54 | else false 55 | 56 | 57 | /// 58 | /// Finds seperate weakly connected components of the graph and returns sets of nodes 59 | /// 60 | /// The graph to analyse 61 | /// returns set of sets of nodes making up each component. 62 | static member getWeakComponentsOfDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 63 | g.NodeKeys 64 | |> Seq.map(fun k -> DFS.ofDiGraphUndirected k g |> Set.ofSeq) 65 | |> Set.ofSeq 66 | 67 | /// 68 | /// Finds the largest weakly connected component and returns it's size. 69 | /// 70 | /// The graph to analyse 71 | /// returns an int indicating numner of nodes 72 | static member getLargestWeakComponentSizeOfDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>)= 73 | g 74 | |> Components.getWeakComponentsOfDiGraph 75 | |> Set.map(fun s -> s.Count) 76 | |> Set.toSeq 77 | |> Seq.max 78 | 79 | /// 80 | /// Finds the largest weakly connected component and returns it as a new graph 81 | /// 82 | /// The graph to analyse 83 | /// returns a new graph 84 | static member getLargestWeakComponentOfDiGraph (g: DiGraph<'NodeKey, 'NodeData, 'EdgeData>)= 85 | g 86 | |> Components.getWeakComponentsOfDiGraph 87 | |> Seq.sortByDescending(fun c -> c |> Set.count) 88 | |> Seq.head 89 | |> fun c -> 90 | DiGraph.empty 91 | |> DiGraph.addNodes (c |> Set.toArray) 92 | |> DiGraph.addEdges ( 93 | DiGraph.getAllEdges g 94 | |> Array.filter(fun (f,t,_) -> 95 | (c |> Set.map fst).Contains f && (c |> Set.map fst).Contains t) 96 | ) 97 | 98 | 99 | static member compute (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) :seq>= 100 | Components.getGraphComponentsOfAdjGraph graph 101 | -------------------------------------------------------------------------------- /docs/reference/_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{fsdocs-page-title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | {{fsdocs-watch-script}} 25 | 26 | 27 | 28 |
29 |
30 | 63 |
64 |
65 |
66 |
67 | {{fsdocs-content}} 68 |
69 |
70 |
71 | {{fsdocs-tooltips}} 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Radius.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | open Graphoscope 3 | open FSharpAux 4 | 5 | type Radius() = 6 | 7 | /// 8 | /// Get the radius of graph calculated by their minimum Eccentricity 9 | /// 10 | /// Result of the FloydWarshall shortest Path calculation of a graph 11 | /// A float of the shortest shortest Paths of a graph 12 | static member ofGraph2D (floydWarshall : float [,]) = 13 | ///Returns the element of the array which is the smallest after projection according to the Operators.min operator 14 | let minBy projection (arr: _ [,]) = 15 | let n,m = arr |> Array2D.length1, arr |> Array2D.length2 16 | let rec compareMin i j min = 17 | if j = m then min 18 | elif i=j then 19 | compareMin i (j+1) min 20 | else 21 | let value = arr.[i,j] 22 | if (projection value) > (projection min) then compareMin i (j+1) min 23 | else compareMin i (j+1) value 24 | let rec countRow min i = 25 | if i = n then min 26 | else countRow (compareMin i 0 min) (i+1) 27 | countRow infinity 0 28 | 29 | floydWarshall 30 | |> minBy id 31 | 32 | /// 33 | /// Get the radius of graph calculated by their minimum Eccentricity 34 | /// 35 | /// Function to get a float edgeweight of the EdgeData 36 | /// The graph to calculate the radius for 37 | /// A float of the longest shortest Path of a graph 38 | static member ofFGraph (weigthF:'EdgeData->float) (graph:FGraph<'NodeKey,'NodeData,'EdgeData>) = 39 | Eccentricity.ofFGraph weigthF graph 40 | |> Seq.minBy snd 41 | |> snd 42 | 43 | /// 44 | /// Get the radius of graph calculated by their minimum Eccentricity 45 | /// 46 | /// Function to get a float edgeweight of the EdgeData 47 | /// The graph to calculate the radius for 48 | /// A float of the longest shortest Path of a graph 49 | static member ofAdjGraph (weigthF:'EdgeData->float) (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) = 50 | Eccentricity.ofAdjGraph weigthF graph 51 | |> Seq.minBy snd 52 | |> snd 53 | 54 | /// 55 | /// Get the radius of graph calculated by their minimum Eccentricity 56 | /// 57 | /// The graph to calculate the radius for 58 | /// A float of the longest shortest Path of a graph 59 | static member compute((graph:FGraph<'NodeKey,'NodeData,'EdgeData>)) = 60 | Radius.ofFGraph (fun x -> 1.) graph 61 | 62 | /// 63 | /// Get the radius of graph calculated by their minimum Eccentricity 64 | /// 65 | /// The graph to calculate the radius for 66 | /// A float of the longest shortest Path of a graph 67 | static member compute((graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>)) = 68 | Radius.ofAdjGraph (fun x -> 1.) graph 69 | 70 | 71 | /// 72 | /// Get the radius of graph calculated by their minimum Eccentricity 73 | /// 74 | /// The graph to calculate the radius for 75 | /// A float of the longest shortest Path of a graph 76 | static member computeWithEdgeData((graph:FGraph<'NodeKey,'NodeData,float>)) = 77 | Radius.ofFGraph id graph 78 | 79 | /// 80 | /// Get the radius of graph calculated by their minimum Eccentricity 81 | /// 82 | /// The graph to calculate the radius for 83 | /// A float of the longest shortest Path of a graph 84 | static member computeWithEdgeData((graph:AdjGraph<'NodeKey,'NodeData,float>)) = 85 | Radius.ofAdjGraph id graph 86 | 87 | /// 88 | /// Get the radius of graph calculated by their minimum Eccentricity 89 | /// 90 | /// The graph to calculate the radius for 91 | /// A float of the longest shortest Path of a graph 92 | static member computeWithEdgeDataBy((weightF:'EdgeData -> float),(graph:FGraph<'NodeKey,'NodeData,'EdgeData>)) = 93 | Radius.ofFGraph weightF graph 94 | 95 | /// 96 | /// Get the radius of graph calculated by their minimum Eccentricity 97 | /// 98 | /// The graph to calculate the radius for 99 | /// A float of the longest shortest Path of a graphcomputeWithEdgeDataBy 100 | static member computeWithEdgeDataBy((weightF:'EdgeData -> float),(graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>)) = 101 | Radius.ofAdjGraph weightF graph 102 | -------------------------------------------------------------------------------- /docs/_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{fsdocs-page-title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | {{fsdocs-watch-script}} 34 | 35 | 36 | 37 |
38 |
39 | 73 |
74 |
75 |
76 |
77 | {{fsdocs-content}} 78 |
79 |
80 |
81 | {{fsdocs-tooltips}} 82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/M_4_CentralityMeasures.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: Centrality 4 | category: Measures 5 | categoryindex: 2 6 | index: 4 7 | --- 8 | *) 9 | 10 | (*** hide ***) 11 | 12 | (*** condition: prepare ***) 13 | #r "nuget: FSharpAux.Core, 2.0.0" 14 | #r "nuget: FSharpAux.IO, 2.0.0" 15 | #r "nuget: FSharp.Data, 6.2.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "nuget: OptimizedPriorityQueue, 5.1.0" 18 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 19 | 20 | (*** condition: ipynb ***) 21 | #if IPYNB 22 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 23 | #endif // IPYNB 24 | 25 | (** 26 | # Intoduction to GraphCentrality using FGraph 27 | Graph centrality is a concept used in network analysis to identify and measure the relative importance or significance of nodes within a graph. 28 | It helps us understand which nodes play a central role in the network, indicating their influence, importance, or prominence. 29 | Centrality measures can be applied to various types of networks, including social networks, transportation networks, communication networks, and more. 30 | ## Creating a graph by reading a complete graph representation as one. 31 | Step 1 is the creation of a graph example where we can visualise the graph centrality. 32 | We will visualise the graph via Cytopscape 33 | *) 34 | open Graphoscope 35 | open Cytoscape.NET 36 | open FSharpAux 37 | 38 | let centralityEdges = 39 | seq{ 40 | 1,1,2,2,1. 41 | 1,1,4,4,1. 42 | 1,1,5,5,1. 43 | 1,1,6,6,1. 44 | 2,2,3,3,1. 45 | } 46 | 47 | let centralityGraph = AdjGraph.ofSeq centralityEdges 48 | 49 | let renderCyGraph (nodeLabelF:int -> CyParam.CyStyleParam ) = 50 | 51 | CyGraph.initEmpty () 52 | |> CyGraph.withElements [ 53 | for (sk,s,tk,t,el) in (AdjGraph.toSeq centralityGraph) do 54 | let sk, tk = (string sk), (string tk) 55 | yield Elements.node sk [ nodeLabelF s ] 56 | yield Elements.node tk [ nodeLabelF t ] 57 | yield Elements.edge (sprintf "%s_%s" sk tk) sk tk [ ] 58 | ] 59 | |> CyGraph.withStyle "node" 60 | [ 61 | CyParam.content =. CyParam.label 62 | CyParam.color "#A00975" 63 | ] 64 | |> CyGraph.withStyle "edge" 65 | [ 66 | CyParam.content =. CyParam.label 67 | CyParam.Curve.style "bezier" 68 | CyParam.color "#438AFE" 69 | ] 70 | |> CyGraph.withLayout ( 71 | Layout.initBreadthfirst(Layout.LayoutOptions.Generic()) 72 | ) 73 | |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false)) 74 | |> CyGraph.withSize(800, 400) 75 | |> Cytoscape.NET.HTML.toGraphHTML() 76 | 77 | renderCyGraph (fun x -> CyParam.label $"Node: {x}") 78 | (*** include-it-raw ***) 79 | (** 80 | ## Closeness Centrality 81 | Closeness centrality assesses how quickly a node can reach all other nodes in the network. 82 | Nodes with high closeness centrality are considered central because they are close to many other nodes in terms of geodesic distance (the shortest path). 83 | *) 84 | 85 | let closenessCentrality = 86 | Measures.ClosenessCentrality.computeWithEdgeData centralityGraph 87 | 88 | 89 | renderCyGraph (fun x -> CyParam.label ($"Node: {x};Closeness: {((closenessCentrality.Item x)|>Math.round 3)}")) 90 | (*** include-it-raw ***) 91 | 92 | (** 93 | ## Betweenness Centrality 94 | Betweenness centrality measures how often a node lies on the shortest path between pairs of other nodes. 95 | Nodes with high betweenness centrality act as bridges or intermediaries in the network. 96 | *) 97 | 98 | let betweenness = 99 | Measures.BetweennessCentrality.computeWithEdgeData centralityGraph 100 | 101 | renderCyGraph (fun x -> CyParam.label ($"Node: {x};Betweenness: {betweenness.Item x}")) 102 | (*** include-it-raw ***) 103 | 104 | (** 105 | ## Node Eccentricity 106 | Node eccentricity is a concept used in graph theory and network analysis to measure the centrality or importance of a node within a graph. 107 | It quantifies how far a node is from the farthest other node in the network in terms of the shortest path length. 108 | In other words, it represents the maximum distance between a node and any other node in the graph. 109 | *) 110 | 111 | let eccentricity (node:int) = 112 | Measures.Eccentricity.computeOfNodeWithEdgeData(centralityGraph,node) 113 | 114 | renderCyGraph (fun x -> CyParam.label ($"Node: {x};Eccentricity: {eccentricity x}")) 115 | (*** include-it-raw ***) 116 | 117 | 118 | (** 119 | ## Distances 120 | Another important metric to take into account involves statistics related to all the shortest paths within a graph. 121 | These statistics encompass the Diameter (which represents the longest among the shortest paths), the Radius (representing the shortest of the shortest paths), and the average path length. 122 | As these metrics rely on information from all the shortest paths within a graph, they are fundamentally derived from the results of the Floyd-Warshall algorithm for calculating shortest paths. 123 | Therefore it is wise to calculate this once and reuse it for the calulations. 124 | *) 125 | 126 | 127 | let diameter = 128 | Measures.Diameter.ofAdjGraph id centralityGraph 129 | 130 | let radius = 131 | Measures.Radius.ofAdjGraph id centralityGraph 132 | 133 | (***hide***) 134 | $"The given graph has a diameter of {diameter} and a radius of {radius}." 135 | (*** include-it***) 136 | -------------------------------------------------------------------------------- /docs/G_AdjGraph_4_Intro.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: AdjGraph 4 | category: Graphoscope 5 | categoryindex: 1 6 | index: 4 7 | --- 8 | *) 9 | (*** hide ***) 10 | 11 | (*** condition: prepare ***) 12 | #r "nuget: FSharpAux.Core, 2.0.0" 13 | #r "nuget: FSharpx.Collections, 3.1.0" 14 | #r "nuget: FSharp.Data, 6.2.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "nuget: Plotly.NET, 4.1.0" 18 | #r "nuget: Plotly.NET.Interactive, 4.1.0" 19 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 20 | 21 | (*** condition: ipynb ***) 22 | #if IPYNB 23 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 24 | #endif // IPYNB 25 | 26 | (** 27 | # What is AdjGraph 28 | 29 | The AdjGraph is an undirected adaptation of the FGraph. 30 | It combines the predecessors and successors into just one element called neighbours. 31 | 32 | [![](https://mermaid.ink/img/pako:eNpdjssOgjAQRX-FzAoT-AEWJgjIjo3urIuxHR7yKCltDCH8u7VijM5qcs7NzF2AS0EQQdnJB69Rae-cssGzE19icc8VjvXVC8O9d_ALG21p3m3e0cRRm-Q08I9KnEqdSlHjD878gpqqvkmjpk1kThz_Hrxp7mei-l6BAHpSPTbCll5ehIGuqScGkV0FqpYBG1abQ6PlaR44RFoZCsCMAjWlDVYKe4hK7CZan7YATRE?type=png)](https://mermaid.live/edit#pako:eNpdjssOgjAQRX-FzAoT-AEWJgjIjo3urIuxHR7yKCltDCH8u7VijM5qcs7NzF2AS0EQQdnJB69Rae-cssGzE19icc8VjvXVC8O9d_ALG21p3m3e0cRRm-Q08I9KnEqdSlHjD878gpqqvkmjpk1kThz_Hrxp7mei-l6BAHpSPTbCll5ehIGuqScGkV0FqpYBG1abQ6PlaR44RFoZCsCMAjWlDVYKe4hK7CZan7YATRE) 33 | 34 | # Quickstart 35 | ## Creating an empty graph and filling it with single elements 36 | Begin by creating an empty graph,meaning a data structure with no nodes or edges. 37 | Then populate the graph with single elements, individual nodes are added one by one, and edges can be introduced to establish connections between them. 38 | *) 39 | 40 | open Graphoscope 41 | open AdjGraph 42 | 43 | let graphToFillAdjGraph = 44 | 45 | AdjGraph.empty 46 | |> AdjGraph.addNode 1 "1" 47 | |> AdjGraph.addNode 2 "2" 48 | |> AdjGraph.addEdge 1 2 1. 49 | 50 | 51 | (***hide***) 52 | let graphToFillOutput = sprintf "You have created a graph with %i nodes and %i edges" (AdjGraph.countNodes graphToFillAdjGraph) (AdjGraph.countEdges graphToFillAdjGraph) 53 | (*** include-value: graphToFillOutput ***) 54 | 55 | (** 56 | # Working with Graphs 57 | 58 | ## Creating a Graph using AdjGraph 59 | ### Creating an empty graph and add collections of elements 60 | Another way of creating a graph is by filling it with collections of nodes and edges as seen below: 61 | 62 | *) 63 | let graphToFillAdjGraph' = 64 | 65 | let nodes = List.init 100 (fun x -> x,$"{x}") 66 | 67 | let edges = List.init 45 (fun x -> x,x*2,1.) 68 | 69 | AdjGraph.empty 70 | |> AdjGraph.addNodes nodes 71 | |> AdjGraph.addEdges edges 72 | 73 | (***hide***) 74 | let graphToFill2Output = sprintf "You have created a graph with %i nodes and %i edges"(AdjGraph.countNodes graphToFillAdjGraph') (AdjGraph.countEdges graphToFillAdjGraph') 75 | (*** include-value: graphToFill2Output ***) 76 | 77 | (** 78 | ### Removing Nodes and Edges 79 | To remove Nodes or Edges you can just use the remove functions provided: 80 | *) 81 | let graphWithRemovedElementsAdjGraph = 82 | graphToFillAdjGraph' 83 | |> AdjGraph.removeEdge 1 2 84 | |> AdjGraph.removeNode 0 85 | 86 | (***hide***) 87 | let removing = sprintf "You have reduced the graph to %i nodes and %i edges" (AdjGraph.countNodes graphWithRemovedElementsAdjGraph) (AdjGraph.countEdges graphWithRemovedElementsAdjGraph) 88 | (*** include-value: removing ***) 89 | 90 | (** 91 | ## 92 | 93 | # From Data 94 | ## Import a graph 95 | 96 | This is the well-known and much-used Zachary karate club network. The data was collected from the members of a university karate club by Wayne Zachary in 1977. 97 | Each node represents a member of the club, and each edge represents a tie between two members of the club. 98 | The network is undirected. 99 | *) 100 | 101 | open FSharp.Data 102 | 103 | let getElementOfFile (fullpath: string) (delimiter: string) (headerRows: int) (weightsIncluded: bool) = 104 | let rows = CsvFile.Load(fullpath, delimiter, skipRows = headerRows, hasHeaders = false).Rows 105 | rows 106 | |> Seq.map (fun row -> int row[0],int row[0], int row[1],int row[1], if weightsIncluded then float row[2] else 1.0) 107 | 108 | 109 | let karateFileAdj= __SOURCE_DIRECTORY__ + "/../tests/Graphoscope.Tests/ReferenceGraphs/zachary.txt" 110 | 111 | let karateGraphAdj = 112 | let g = AdjGraph.empty 113 | getElementOfFile karateFileAdj " " 2 false 114 | |> Seq.iter(fun (s1,s2,t1,t2,w: float) -> AdjGraph.addElement s1 s2 t1 t2 w g|>ignore) 115 | g 116 | 117 | 118 | (** 119 | let's use Cytoscape.NET for visualization: 120 | *) 121 | 122 | open Cytoscape.NET 123 | let vizGraph = 124 | CyGraph.initEmpty () 125 | |> CyGraph.withElements [ 126 | for (sk,s,tk,t,el) in (AdjGraph.toSeq karateGraphAdj) do 127 | let sk, tk = (string sk), (string tk) 128 | yield Elements.node sk [ CyParam.label s ] 129 | yield Elements.node tk [ CyParam.label t ] 130 | yield Elements.edge (sprintf "%s_%s" sk tk) sk tk [ CyParam.label el ] 131 | ] 132 | |> CyGraph.withStyle "node" 133 | [ 134 | CyParam.content =. CyParam.label 135 | CyParam.color "#A00975" 136 | ] 137 | |> CyGraph.withLayout(Cytoscape.NET.Layout.initBreadthfirst(id)) 138 | 139 | (***hide***) 140 | vizGraph 141 | |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false)) 142 | |> CyGraph.withSize(800, 400) 143 | |> HTML.toGraphHTML() 144 | (*** include-it-raw ***) 145 | 146 | -------------------------------------------------------------------------------- /docs/G_Digraph_2_Intro.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: DiGraph 4 | category: Graphoscope 5 | categoryindex: 1 6 | index: 2 7 | --- 8 | *) 9 | (*** hide ***) 10 | 11 | (*** condition: prepare ***) 12 | #r "nuget: FSharpAux.Core, 2.0.0" 13 | #r "nuget: FSharpx.Collections, 3.1.0" 14 | #r "nuget: FSharp.Data, 6.2.0" 15 | #r "nuget: Plotly.NET, 4.1.0" 16 | Plotly.NET.Defaults.DefaultDisplayOptions <- 17 | Plotly.NET.DisplayOptions.init (PlotlyJSReference = Plotly.NET.PlotlyJSReference.NoReference) 18 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 19 | 20 | (*** condition: ipynb ***) 21 | #if IPYNB 22 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 23 | #endif // IPYNB 24 | 25 | (** 26 | # Datastructure 27 | The DiGraph is a graph representation that uses a fast node-index-based lookup of edge data, utilizing ResizeArrays for efficient storage and retrieval of information. 28 | The structure is visualised here: 29 | 30 | [![](https://mermaid.ink/img/pako:eNpdkM8KwjAMxl9FclLQF9hBUDt1iF70ZLtDWKMrunbUDhHx3U39Q8Wevv6-JB_JHSqnCTI4nN21qtGH3k4o2-M3kcIsPLZ1ORqNp32j19gOPhYTIV0Xcn2kS5lgLo39ZVNmM7nhiBXdPkwwK94yTzLSuYy9AgOWyV_8w5iz_M78Bi1jUMosXn4hog1DaMg3aDSveY81CkJNDSnIWGr0JwXKPrgOu-C2N1tBFnxHQ-hajYGEwaPHBrIDni9MW7R759KftAnOr993fJ3z8QQLO2iF?type=png)](https://mermaid.live/edit#pako:eNpdkM8KwjAMxl9FclLQF9hBUDt1iF70ZLtDWKMrunbUDhHx3U39Q8Wevv6-JB_JHSqnCTI4nN21qtGH3k4o2-M3kcIsPLZ1ORqNp32j19gOPhYTIV0Xcn2kS5lgLo39ZVNmM7nhiBXdPkwwK94yTzLSuYy9AgOWyV_8w5iz_M78Bi1jUMosXn4hog1DaMg3aDSveY81CkJNDSnIWGr0JwXKPrgOu-C2N1tBFnxHQ-hajYGEwaPHBrIDni9MW7R759KftAnOr993fJ3z8QQLO2iF) 31 | 32 | # Quickstart 33 | 34 | We can build a graph from scratch. Either by passing an array of edges: 35 | *) 36 | open Graphoscope 37 | open DiGraph 38 | 39 | [|(0, 1, 1.0); (0, 2, 1.0); (1, 1, 1.0); (1, 3, 1.0); (3, 2, 1.0); (4, 0, 1.0)|] 40 | |> DiGraph.createFromEdges 41 | 42 | (** 43 | or by creating an empty graph and adding the nodes and edges one by one. 44 | The int and float after the "create" define the type of the nodes and edges. 45 | *) 46 | 47 | let emptyGraph :DiGraph = DiGraph.empty 48 | 49 | let edge = (1,3, 1.0) 50 | 51 | let filledGraph = 52 | emptyGraph 53 | |> DiGraph.addNode 1 1 54 | |> DiGraph.addNode 2 2 55 | |> DiGraph.addNode 3 3 56 | |> DiGraph.addEdge edge 57 | 58 | (***hide***) 59 | let filledGraphNodes = filledGraph |> DiGraph.countNodes|> sprintf "Manually created a graph with %i nodes" 60 | (*** include-value: filledGraphNodes ***) 61 | 62 | (** 63 | # Working with Graphs 64 | 65 | # From Data 66 | For illustration, we will import a directed graph from The [KONECT Project](http://konect.cc/) website. This is an excellent resource with many graphs in an edge list based format which is simple 67 | to import and analyse using Graphoscope. We will start by importing a [graph](http://konect.cc/networks/moreno_rhesus/) describing the grooming interactions between rhesus monkeys. 68 | 69 | Create a monkeys.fsx and run the following code to import and print some basic measures. Further documention of DiGraph functionality is [here](reference/graphoscope-digraph.html) 70 | *) 71 | 72 | open FSharp.Data 73 | 74 | let getElementOfFile (fullpath: string) (delimiter: string) (headerRows: int) (weightsIncluded: bool) = 75 | let rows = CsvFile.Load(fullpath, delimiter, skipRows = headerRows, hasHeaders = false).Rows 76 | rows 77 | |> Seq.map (fun row -> int row[0],int row[0], int row[1],int row[1], if weightsIncluded then float row[2] else 1.0) 78 | 79 | 80 | let file = __SOURCE_DIRECTORY__ + "/../tests/Graphoscope.Tests/ReferenceGraphs/out.moreno_rhesus_rhesus.txt" 81 | let monkeyGraph = 82 | 83 | CsvFile.Load(file, " ", skipRows = 2, hasHeaders = false).Rows 84 | |> Seq.map (fun row -> 85 | int row[0],int row[0], int row[1],int row[1], float row[2]) 86 | |> DiGraph.ofSeq 87 | 88 | 89 | 90 | (***hide***) 91 | let outputMonkeyGraph = sprintf "Successfully imported the graph! It has %i nodes and %i edges. The average degree is %f " (DiGraph.countNodes monkeyGraph) (DiGraph.countEdges monkeyGraph) (Measures.Degree.average monkeyGraph) 92 | (*** include-value: outputMonkeyGraph ***) 93 | 94 | 95 | (** 96 | We can also import undirected graphs using the [Graph](reference/graphoscope-graph.html) namespace. These examples use the [Karate club](http://konect.cc/networks/ucidata-zachary/) graph. 97 | *) 98 | 99 | let karateFile= __SOURCE_DIRECTORY__ + "/../tests/Graphoscope.Tests/ReferenceGraphs/zachary.txt" 100 | let karateGraph = 101 | let g = DiGraph.empty 102 | getElementOfFile karateFile " " 2 false 103 | |> Seq.iter(fun (s1,s2,t1,t2,w: float) -> DiGraph.addElement s1 s2 t1 t2 w g|>ignore) 104 | g 105 | 106 | (***hide***) 107 | let karateOutput = sprintf "Successfully imported the undirected karate graph! It has %i nodes and %i edges. The average degree is %f " (DiGraph.countNodes karateGraph) (DiGraph.countEdges karateGraph) (Measures.Degree.average karateGraph) 108 | (*** include-value: karateOutput ***) 109 | 110 | (** 111 | A conversion into an Adjacency Matrix is also very easily achievable. It can be executed as follows. 112 | *) 113 | let monkeyAdjacencyMatrix = DiGraph.toAdjacencyMatrix id monkeyGraph 114 | (***include-value: monkeyAdjacencyMatrix***) 115 | 116 | (** 117 | ## Charting 118 | Consider using [Plotly.NET](https://plotly.net/) for charting. Built on top of plotly.js, it is a mature library offering a wide range of customisable charts. 119 | Here is a basic example showing degree distribution within the Karate club. 120 | *) 121 | #r "nuget: Plotly.NET, 4.1.0" 122 | open Plotly.NET 123 | 124 | Measures.Degree.sequence karateGraph 125 | |> Chart.Histogram 126 | |> GenericChart.toChartHTML 127 | (***include-it-raw***) 128 | -------------------------------------------------------------------------------- /tests/Graphoscope.Tests/Degree.fs: -------------------------------------------------------------------------------- 1 | module Degree 2 | 3 | open System 4 | open Xunit 5 | open Graphoscope 6 | open Graphoscope.DiGraph 7 | open System.IO 8 | open FSharpAux 9 | 10 | [] 11 | let ``Monkey FGraph has correct measures`` () = 12 | 13 | //measures taken from http://konect.cc/networks/moreno_rhesus/ 14 | let file = Path.Combine(Environment.CurrentDirectory, "ReferenceGraphs/out.moreno_rhesus_rhesus.txt") 15 | 16 | let monkeyGraph = 17 | File.ReadLines file 18 | |> Seq.skip 2 19 | |> Seq.map 20 | (fun str -> 21 | let arr = str.Split(' ') 22 | int arr.[0], arr.[0], int arr.[1], arr.[1], float arr.[2]) 23 | |> FGraph.ofSeq 24 | 25 | let degreeAverage = 13.8750 26 | let degreeMax = 20 27 | let degreeMin = 4 28 | let degreeDist:Set = 29 | seq{ 30 | 4.000000 31 | 7.000000 32 | 8.000000 33 | 8.000000 34 | 9.000000 35 | 11.000000 36 | 15.000000 37 | 15.000000 38 | 15.000000 39 | 16.000000 40 | 18.000000 41 | 18.000000 42 | 19.000000 43 | 19.000000 44 | 20.000000 45 | 20.000000 46 | } 47 | |>Set.ofSeq 48 | 49 | let monkeyDist = Measures.Degree.sequence monkeyGraph|>Set.ofSeq 50 | 51 | Assert.Equal(degreeAverage,(Measures.Degree.average monkeyGraph)) 52 | Assert.Equal(degreeMax,(Measures.Degree.maximum monkeyGraph)) 53 | Assert.Equal(degreeMin,(Measures.Degree.minimum monkeyGraph)) 54 | Assert.True(((Set.intersect degreeDist monkeyDist) = degreeDist)) 55 | Assert.True(((Set.intersect monkeyDist degreeDist) = degreeDist)) 56 | Assert.Equal(12,(Measures.InDegree.maximum monkeyGraph)) 57 | Assert.Equal(10,(Measures.OutDegree.maximum monkeyGraph)) 58 | 59 | [] 60 | let ``Monkey DiGraph has correct measures`` () = 61 | 62 | //measures taken from http://konect.cc/networks/moreno_rhesus/ 63 | let file = Path.Combine(Environment.CurrentDirectory, "ReferenceGraphs/out.moreno_rhesus_rhesus.txt") 64 | 65 | let monkeyGraph = 66 | File.ReadLines file 67 | |> Seq.skip 2 68 | |> Seq.map 69 | (fun str -> 70 | let arr = str.Split(' ') 71 | int arr.[0], arr.[0], int arr.[1], arr.[1], float arr.[2]) 72 | |> DiGraph.ofSeq 73 | 74 | let degreeAverage = 13.8750 75 | let degreeMax = 20 76 | let degreeMin = 4 77 | let degreeSequence = [|20; 20; 19; 19; 18; 18; 16; 15; 15; 15; 11; 9; 8; 8; 7; 4|] 78 | 79 | let monkeySequence = Measures.Degree.sequence monkeyGraph 80 | 81 | Assert.Equal(degreeAverage,(Measures.Degree.average monkeyGraph)) 82 | Assert.Equal(degreeMax,(Measures.Degree.maximum monkeyGraph)) 83 | Assert.Equal(degreeMin,(Measures.Degree.minimum monkeyGraph)) 84 | Assert.Equal(degreeSequence, monkeySequence) 85 | Assert.Equal(12,(Measures.InDegree.maximum monkeyGraph)) 86 | Assert.Equal(10,(Measures.OutDegree.maximum monkeyGraph)) 87 | 88 | [] 89 | let `` Simple FGraph has correct In and Out Degree Measures`` () = 90 | // InDegree // 91 | 92 | let elementsInGraph = 93 | seq 94 | { 95 | 0,0,1,1,1 96 | 1,1,2,2,1 97 | 2,2,3,3,1 98 | 3,3,4,4,1 99 | 4,4,0,0,1 100 | } 101 | 102 | let inGraph = FGraph.ofSeq elementsInGraph 103 | 104 | let averageDegreeIn = 1. 105 | let maxIn = 1. 106 | let minIn = 1. 107 | let distIn = 108 | Seq.init 5 (fun x -> 1.)|>Set.ofSeq 109 | 110 | let distInDegree = Measures.InDegree.sequence inGraph |>Set.ofSeq 111 | 112 | Assert.Equal(averageDegreeIn,(Measures.InDegree.average inGraph)) 113 | Assert.Equal(maxIn,(Measures.InDegree.maximum inGraph)) 114 | Assert.Equal(minIn,(Measures.InDegree.minimum inGraph)) 115 | Assert.True(((Set.intersect distInDegree distIn) = distIn)) 116 | Assert.True(((Set.intersect distIn distInDegree) = distIn)) 117 | 118 | 119 | // OutDegree // 120 | 121 | let elementsOutGraph = 122 | elementsInGraph 123 | |>Seq.map(fun (s,s1,t,t1,w) -> 124 | t,t1,s,s1,w 125 | ) 126 | 127 | let outGraph = FGraph.ofSeq elementsOutGraph 128 | 129 | let distOutDegree = Measures.OutDegree.sequence inGraph |>Set.ofSeq 130 | 131 | Assert.Equal(averageDegreeIn,(Measures.OutDegree.average outGraph)) 132 | Assert.Equal(maxIn,(Measures.OutDegree.maximum outGraph)) 133 | Assert.Equal(minIn,(Measures.OutDegree.minimum outGraph)) 134 | Assert.True(((Set.intersect distOutDegree distIn) = distIn)) 135 | Assert.True(((Set.intersect distIn distOutDegree) = distIn)) 136 | 137 | 138 | 139 | [] 140 | let `` Simple DiGraph has correct In and Out Degree Measures`` () = 141 | // InDegree // 142 | 143 | let elementsInGraph = 144 | seq 145 | { 146 | 0,0,1,1,1 147 | 1,1,2,2,1 148 | 2,2,3,3,1 149 | 3,3,4,4,1 150 | 4,4,0,0,1 151 | } 152 | 153 | let graph = DiGraph.ofSeq elementsInGraph 154 | 155 | let averageDegreeIn = 1. 156 | let maxIn = 1. 157 | let minIn = 1. 158 | let distIn = [|1; 1; 1; 1; 1;|] 159 | let distOut = [|1; 1; 1; 1; 1;|] 160 | 161 | let distInDegree = Measures.InDegree.sequence graph 162 | let distOutDegree = Measures.OutDegree.sequence graph 163 | 164 | Assert.Equal(averageDegreeIn,(Measures.InDegree.average graph)) 165 | Assert.Equal(maxIn,(Measures.InDegree.maximum graph)) 166 | Assert.Equal(minIn,(Measures.InDegree.minimum graph)) 167 | Assert.Equal(distInDegree, distIn) 168 | Assert.Equal(distOutDegree, distOut) 169 | 170 | -------------------------------------------------------------------------------- /docs/G_FGraph_3_Intro.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | --- 3 | title: FGraph 4 | category: Graphoscope 5 | categoryindex: 1 6 | index: 3 7 | --- 8 | *) 9 | (*** hide ***) 10 | 11 | (*** condition: prepare ***) 12 | #r "nuget: FSharpAux.Core, 2.0.0" 13 | #r "nuget: FSharpx.Collections, 3.1.0" 14 | #r "nuget: FSharp.Data, 6.2.0" 15 | #r "nuget: FSharpAux.IO, 2.0.0" 16 | #r "nuget: Cytoscape.NET, 0.2.0" 17 | #r "nuget: Plotly.NET, 4.1.0" 18 | #r "nuget: Plotly.NET.Interactive, 4.1.0" 19 | #r "../src/Graphoscope/bin/Release/netstandard2.0/Graphoscope.dll" 20 | 21 | (*** condition: ipynb ***) 22 | #if IPYNB 23 | #r "nuget: Graphoscope, {{fsdocs-package-version}}" 24 | #endif // IPYNB 25 | 26 | (** 27 | # What is FGraph 28 | 29 | The FGraph is an adaptation of a functional graph. 30 | In programming, a functional graph typically refers to a data structure or representation used to model and analyze the flow of data or the dependencies between functions in a functional programming paradigm. 31 | Functional programming emphasizes the use of pure functions and avoids mutable state, making functional graphs particularly useful for understanding and optimizing the execution of functional programs. 32 | It is compromised of a Dictionary, containing the nodeIndex as Key and the so called FContext as Value. The structure is visualised here: 33 | 34 | 35 | [![](https://mermaid.ink/img/pako:eNpNj80KwjAQhF9F9qSgL9CDoI2tIoigJxMPS7Jaf5qUJEWL-O5utaJ7mv1mmGUfoJ0hSOBwdTddoI-9rVC2xzORWe6xKvaj0XjaX3HsQs2g85il_Sx1NtI9djBlKOTakyFNITgf9j9jJtsGgRH_YCY3tdaB_rKCcS4n5oyarG46mrX0I3OW83fZkr52yxZyZo6fAzCEknyJJ8OPPdqIglhQSQoSlgb9RYGyT85hHd2msRqS6GsaQl0ZjCROePRYQnLAa2Baod05992fLxMxYKE?type=png)](https://mermaid.live/edit#pako:eNpNj80KwjAQhF9F9qSgL9CDoI2tIoigJxMPS7Jaf5qUJEWL-O5utaJ7mv1mmGUfoJ0hSOBwdTddoI-9rVC2xzORWe6xKvaj0XjaX3HsQs2g85il_Sx1NtI9djBlKOTakyFNITgf9j9jJtsGgRH_YCY3tdaB_rKCcS4n5oyarG46mrX0I3OW83fZkr52yxZyZo6fAzCEknyJJ8OPPdqIglhQSQoSlgb9RYGyT85hHd2msRqS6GsaQl0ZjCROePRYQnLAa2Baod05992fLxMxYKE) 36 | 37 | 38 | # Quickstart 39 | ## Creating an empty graph and filling it with single elements 40 | Begin by creating an empty graph,meaning a data structure with no nodes or edges. 41 | Then populate the graph with single elements, individual nodes are added one by one, and edges can be introduced to establish connections between them. 42 | *) 43 | 44 | open Graphoscope 45 | open FGraph 46 | 47 | let graphToFill = 48 | 49 | FGraph.empty 50 | |> FGraph.addNode 1 "1" 51 | |> FGraph.addNode 2 "2" 52 | |> FGraph.addEdge 1 2 1. 53 | 54 | 55 | (***hide***) 56 | let graphToFillOutput = sprintf "You have created a graph with %i nodes and %i edges" (FGraph.countNodes graphToFill) (FGraph.countEdges graphToFill) 57 | (*** include-value: graphToFillOutput ***) 58 | 59 | (** 60 | # Working with Graphs 61 | 62 | ## Creating a Graph using FGraph 63 | ### Creating an empty graph and add collections of elements 64 | Another way of creating a graph is by filling it with collections of nodes and edges as seen below: 65 | 66 | *) 67 | let graphToFill' = 68 | 69 | let nodes = List.init 100 (fun x -> x,$"{x}") 70 | 71 | let edges = List.init 45 (fun x -> x,x*2,1.) 72 | 73 | FGraph.empty 74 | |> FGraph.addNodes nodes 75 | |> FGraph.addEdges edges 76 | 77 | (***hide***) 78 | let graphToFill2Output = sprintf "You have created a graph with %i nodes and %i edges"(FGraph.countNodes graphToFill') (FGraph.countEdges graphToFill') 79 | (*** include-value: graphToFill2Output ***) 80 | 81 | (** 82 | ### Removing Nodes and Edges 83 | To remove Nodes or Edges you can just use the remove functions provided: 84 | *) 85 | let graphWithRemovedElements = 86 | graphToFill' 87 | |> FGraph.removeEdge 1 2 88 | |> FGraph.removeNode 0 89 | 90 | (***hide***) 91 | let removing = sprintf "You have reduced the graph to %i nodes and %i edges" (FGraph.countNodes graphWithRemovedElements) (FGraph.countEdges graphWithRemovedElements) 92 | (*** include-value: removing ***) 93 | 94 | (** 95 | ## 96 | 97 | # From Data 98 | ## Import a graph 99 | 100 | This directed network contains observed grooming episodes between free ranging rhesus macaques (Macaca mulatta) 101 | in Cayo Santiago during a two month period in 1963. Cayo Santiago is an island off the coast of Puerto Rico, also 102 | known as Isla de los monos (Island of the monkeys). A node represents a monkey and a directed edge A → B denotes 103 | that the rhesus macaque A groomed rhesus macaque B. The integer edge weights indicate how often this behaviour was observed. 104 | 105 | 106 | *) 107 | open Graphoscope 108 | 109 | open FSharpAux.IO 110 | open FSharpAux.IO.SchemaReader.Attribute 111 | 112 | 113 | (** 114 | First we model the input domain as a reccord type and read a sequence of MonkeyEdges 115 | *) 116 | type MonkeyEdge = { 117 | [] Source : int 118 | [] Target : int 119 | [] Groomed : int 120 | } 121 | 122 | let monkeyEdges = 123 | Seq.fromFileWithCsvSchema("D:/Source/Graphoscope/tests/Graphoscope.Tests/ReferenceGraphs/out.moreno_rhesus_rhesus.txt",' ',false,skipLines=2 ) 124 | 125 | (** 126 | Convert a MonkeyEdge record to a sequence of graph elements (sourceKey * sourceData * targetKey * targetData * edgeData) 127 | *) 128 | let monkeyGraph = 129 | monkeyEdges 130 | |> Seq.map (fun mke -> 131 | mke.Source, sprintf "Monkey_%i" mke.Source,mke.Target,sprintf "Monkey_%i" mke.Target,float mke.Groomed) 132 | |> FGraph.ofSeq 133 | 134 | (** 135 | let's use Cytoscape.NET for visualization: 136 | *) 137 | 138 | open Cytoscape.NET 139 | let vizGraph = 140 | CyGraph.initEmpty () 141 | |> CyGraph.withElements [ 142 | for (sk,s,tk,t,el) in (FGraph.toSeq monkeyGraph) do 143 | let sk, tk = (string sk), (string tk) 144 | yield Elements.node sk [ CyParam.label s ] 145 | yield Elements.node tk [ CyParam.label t ] 146 | yield Elements.edge (sprintf "%s_%s" sk tk) sk tk [ CyParam.label el ] 147 | ] 148 | |> CyGraph.withStyle "node" 149 | [ 150 | CyParam.content =. CyParam.label 151 | CyParam.color "#A00975" 152 | ] 153 | 154 | (***hide***) 155 | vizGraph 156 | |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false)) 157 | |> CyGraph.withSize(800, 400) 158 | |> HTML.toGraphHTML() 159 | (*** include-it-raw ***) 160 | 161 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/InformationEntropy.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | 3 | open Graphoscope 4 | open System.Collections.Generic 5 | 6 | /// 7 | /// Computes the graph density 8 | /// 9 | type InformationEntropy() = 10 | 11 | /// 12 | /// Computes the information entropy of the given FGraph . 13 | /// 14 | /// The function to get the desired information for the entropy of the nodedata. 15 | /// The graph for which to compute the information entropy. 16 | /// The node for which to compute the information entropy. 17 | /// This calculation does not consider self loops 18 | /// 19 | /// The information entropy of the given node . 20 | /// 21 | static member ofFGraph (labelF:'NodeData -> 'Information) (graph:FGraph<'NodeKey,'NodeData,'EdgeData>) (n:'NodeKey) = 22 | let r = 23 | graph.Item n 24 | |> FContext.neighbours 25 | |> Seq.choose(fun (nk,ed) -> 26 | if nk=n then None 27 | else 28 | Some ( 29 | nk, 30 | graph.Item nk 31 | |>fun (s,d,p) -> d 32 | |>labelF 33 | ) 34 | ) 35 | |> Seq.distinctBy fst 36 | let countAll = Seq.length r|>float 37 | 38 | r 39 | |> Seq.groupBy snd 40 | |> Seq.sumBy(fun (bin,v) -> 41 | let counts = Seq.length v|>float 42 | let p = counts / countAll 43 | let surprise = System.Math.Log10((1./p)) 44 | p * surprise 45 | ) 46 | 47 | /// 48 | /// Computes the information entropy of the given AdjGraph . 49 | /// 50 | /// The function to get the desired information for the entropy of the nodedata. 51 | /// The graph for which to compute the information entropy. 52 | /// The node for which to compute the information entropy. 53 | /// This calculation does not consider self loops 54 | /// 55 | /// The information entropy of the given node . 56 | /// 57 | static member ofAdjGraph (labelF:'NodeData -> 'Information) (graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>) (n:'NodeKey) = 58 | let r = 59 | AdjGraph.getNeighbours n graph 60 | |> Seq.choose(fun (nk,ed) -> 61 | if nk=n then None 62 | else 63 | Some ( 64 | nk, 65 | graph.Item nk 66 | |>fun (d,p) -> d 67 | |>labelF 68 | ) 69 | ) 70 | |> Seq.distinctBy fst 71 | let countAll = Seq.length r|>float 72 | 73 | r 74 | |> Seq.groupBy snd 75 | |> Seq.sumBy(fun (bin,v) -> 76 | let counts = Seq.length v|>float 77 | let p = counts / countAll 78 | let surprise = System.Math.Log10((1./p)) 79 | p * surprise 80 | ) 81 | 82 | 83 | //TODO 84 | /// 85 | /// Computes the information entropy of the given FGraph . 86 | /// 87 | /// The function to get the desired information for the entropy of the nodedata. 88 | /// The graph for which to compute the information entropy. 89 | /// The node for which to compute the information entropy. 90 | /// This calculation does not consider self loops 91 | /// 92 | /// The information entropy of the given node . 93 | /// 94 | static member ofDiGraph (labelF:'NodeData -> 'Information) (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>) (n:'NodeKey) = 95 | System.NotImplementedException() |> raise 96 | 97 | /// 98 | /// Computes the information entropy of the given FGraph . 99 | /// 100 | /// The function to get the desired information for the entropy of the nodedata. 101 | /// The graph for which to compute the information entropy. 102 | /// The node for which to compute the information entropy. 103 | /// This calculation does not consider self loops 104 | /// 105 | /// The information entropy of the given node . 106 | /// 107 | static member compute ((labelF:'NodeData -> 'Information),(graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>),(n:'NodeKey)) = 108 | InformationEntropy.ofDiGraph labelF graph n 109 | 110 | 111 | /// 112 | /// Computes the information entropy of the given FGraph . 113 | /// 114 | /// The function to get the desired information for the entropy of the nodedata. 115 | /// The graph for which to compute the information entropy. 116 | /// The node for which to compute the information entropy. 117 | /// This calculation does not consider self loops 118 | /// 119 | /// The information entropy of the given node . 120 | /// 121 | static member compute ((labelF:'NodeData -> 'Information),(graph:FGraph<'NodeKey,'NodeData,'EdgeData>),(n:'NodeKey)) = 122 | InformationEntropy.ofFGraph labelF graph n 123 | 124 | /// 125 | /// Computes the information entropy of the given FGraph . 126 | /// 127 | /// The function to get the desired information for the entropy of the nodedata. 128 | /// The graph for which to compute the information entropy. 129 | /// The node for which to compute the information entropy. 130 | /// This calculation does not consider self loops 131 | /// 132 | /// The information entropy of the given node . 133 | /// 134 | static member compute ((labelF:'NodeData -> 'Information),(graph:AdjGraph<'NodeKey,'NodeData,'EdgeData>),(n:'NodeKey)) = 135 | InformationEntropy.ofAdjGraph labelF graph n 136 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Eccentricity.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | 3 | open Graphoscope 4 | open System.Collections.Generic 5 | open FSharpAux 6 | 7 | type Eccentricity() = 8 | 9 | /// 10 | /// Get the Eccentricity of a node in an FGraph 11 | /// 12 | /// Function to get the edgeweight out of the 'EdgeData 13 | /// The graph to be analysed 14 | /// The NodeKey to get the Eccentricity of 15 | /// A float of the Eccentricity of the node 16 | static member ofFGraphNode (getEdgeWeightF:'EdgeData -> float) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) (nodeKey:'NodeKey) = 17 | //Get the collection of the shortest Paths by the given Dijkstra function and find the longest shortest path 18 | let dic = Algorithms.Dijkstra.ofFGraph nodeKey getEdgeWeightF graph 19 | let eccentricity = Seq.max(dic.Values) 20 | eccentricity 21 | 22 | /// 23 | /// Get the Eccentricity of a node in an FGraph 24 | /// 25 | /// Function to get the edgeweight out of the 'EdgeData 26 | /// The graph to be analysed 27 | /// The NodeKey to get the Eccentricity of 28 | /// A float of the Eccentricity of the node 29 | static member ofFGraph (getEdgeWeightF:'EdgeData -> float) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 30 | graph.Keys 31 | |> Seq.map(fun k -> 32 | k, 33 | Eccentricity.ofFGraphNode getEdgeWeightF graph k 34 | ) 35 | 36 | /// 37 | /// Get the Eccentricity of a node in an AdjGraph 38 | /// 39 | /// Function to get the edgeweight out of the 'EdgeData 40 | /// The graph to be analysed 41 | /// The NodeKey to get the Eccentricity of 42 | /// A float of the Eccentricity of the node 43 | static member ofAdjGraphNode (getEdgeWeightF:'EdgeData -> float) (graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) (nodeKey:'NodeKey) = 44 | //Get the collection of the shortest Paths by the given Dijkstra function and find the longest shortest path 45 | let dic = Algorithms.Dijkstra.ofAdjGraph nodeKey getEdgeWeightF graph 46 | let eccentricity = Seq.max(dic.Values) 47 | eccentricity 48 | 49 | /// 50 | /// Get the Eccentricity of all nodes in an AdjGraph 51 | /// 52 | /// Function to get the edgeweight out of the 'EdgeData 53 | /// The graph to be analysed 54 | /// The NodeKey to get the Eccentricity of 55 | /// A float of the Eccentricity of the node 56 | static member ofAdjGraph (getEdgeWeightF:'EdgeData -> float) (graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) = 57 | graph.Keys 58 | |> Seq.map(fun k -> 59 | k, 60 | Eccentricity.ofAdjGraphNode getEdgeWeightF graph k 61 | ) 62 | 63 | /// 64 | /// Get the Eccentricity of a graph of its FloydWarshall shortest Path result 65 | /// 66 | /// Result of the FloydWarshall shortest Path calculation of a graph 67 | /// A dictionary with the nodeIndeces as Keys and the Eccentricity as value 68 | static member ofGraph2D (floydWarshallResult : float [,]) = 69 | let shortestPaths = floydWarshallResult 70 | // let indexToNode = graph.Keys|>Seq.map(fun x -> nodeIndexer x,x)|> Map.ofSeq 71 | let dict = new Dictionary() 72 | let getDict (arr: _ [,]) = 73 | let n,m = arr |> Array2D.length1, arr |> Array2D.length2 74 | let rec getMax i j max = 75 | if j = m then max 76 | else 77 | let value = arr.[i,j] 78 | if value < max then getMax i (j+1) max 79 | else getMax i (j+1) value 80 | 81 | for i=0 to (n-1) do 82 | let eccentricity = getMax i 0 arr.[i,0] 83 | let key = i//indexToNode.Item i 84 | dict.Add(key,eccentricity) 85 | 86 | dict 87 | getDict shortestPaths 88 | 89 | static member compute (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 90 | Eccentricity.ofFGraph (fun x -> 1.) graph 91 | 92 | static member computeWithEdgeData (graph : FGraph<'NodeKey, 'NodeData, float>) = 93 | Eccentricity.ofFGraph id graph 94 | 95 | static member computeWithEdgeDataBy ((getEdgeWeightF:'EdgeData -> float),(graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>)) = 96 | Eccentricity.ofFGraph getEdgeWeightF graph 97 | 98 | 99 | static member compute (graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>) = 100 | Eccentricity.ofAdjGraph (fun x -> 1.) graph 101 | 102 | static member computeWithEdgeData (graph : AdjGraph<'NodeKey, 'NodeData, float>) = 103 | Eccentricity.ofAdjGraph id graph 104 | 105 | static member computeWithEdgeDataBy ((getEdgeWeightF:'EdgeData -> float),(graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>)) = 106 | Eccentricity.ofAdjGraph getEdgeWeightF graph 107 | 108 | 109 | static member computeOfNode ((graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>),(nodeKey:'NodeKey)) = 110 | Eccentricity.ofFGraphNode (fun x -> 1.) graph nodeKey 111 | 112 | static member computeOfNodeWithEdgeData ((graph : FGraph<'NodeKey, 'NodeData, float>),(nodeKey:'NodeKey)) = 113 | Eccentricity.ofFGraphNode id graph nodeKey 114 | 115 | static member computeOfNodeWithEdgeDataBy ((getEdgeWeightF:'EdgeData -> float),(graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>),(nodeKey:'NodeKey)) = 116 | Eccentricity.ofFGraphNode getEdgeWeightF graph nodeKey 117 | 118 | static member computeOfNode ((graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>),(nodeKey:'NodeKey)) = 119 | Eccentricity.ofAdjGraphNode (fun x -> 1.) graph nodeKey 120 | 121 | static member computeOfNodeWithEdgeData ((graph : AdjGraph<'NodeKey, 'NodeData, float>),(nodeKey:'NodeKey)) = 122 | Eccentricity.ofAdjGraphNode id graph nodeKey 123 | 124 | static member computeOfNodeWithEdgeDataBy ((getEdgeWeightF:'EdgeData -> float),(graph : AdjGraph<'NodeKey, 'NodeData, 'EdgeData>),(nodeKey:'NodeKey)) = 125 | Eccentricity.ofAdjGraphNode getEdgeWeightF graph nodeKey 126 | -------------------------------------------------------------------------------- /src/Graphoscope.IO/GDF.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.IO 2 | open System.Text 3 | 4 | module GDF = 5 | //Typedefinition, later used to changes the type of the associated value to the correct type. 6 | type GDFValue = 7 | | VARCHAR of string 8 | | BOOLEAN of bool 9 | | DOUBLE of float 10 | | INTEGER of int 11 | 12 | type GDFNode = { 13 | // Node: Node 14 | NodeInfo: seq 15 | } 16 | 17 | type GDFEdge = { 18 | // SourceNode: Node 19 | // TargetNode: Node 20 | EdgeInfo: seq 21 | } 22 | 23 | type GDFItem = 24 | | Node of GDFNode 25 | | Edge of GDFEdge 26 | | Unknown of string 27 | 28 | ////Transforms string into string [], deleting quotes in the progress and splitting at commas 29 | //let splitElementInfos (line:string) = 30 | // let chars = line.ToCharArray() 31 | 32 | // let rec stringDeconstruction insideQuote i vertexInfos (sb:StringBuilder) = 33 | // match chars.[i] with 34 | // // Handle quote marks 35 | // | '\'' when i = chars.Length-1 -> sb.ToString() :: vertexInfos 36 | // | '\'' when insideQuote -> stringDeconstruction false (i+1) vertexInfos (sb) 37 | // | '\'' -> stringDeconstruction true (i+1) vertexInfos (sb) 38 | // // Handle commas 39 | // | ',' when insideQuote -> stringDeconstruction insideQuote (i+1) vertexInfos (sb.Append ',') 40 | // | ',' when i = chars.Length-1 -> sb.ToString() :: "" :: vertexInfos 41 | // | ',' -> stringDeconstruction insideQuote (i+1) (sb.ToString() :: vertexInfos) (sb.Clear()) 42 | // // Handle every other symbol 43 | // | c when i = chars.Length-1 -> sb.Append(c).ToString() :: vertexInfos 44 | // | c -> stringDeconstruction insideQuote (i+1) vertexInfos (sb.Append c) 45 | 46 | // stringDeconstruction false 0 [] (StringBuilder()) 47 | // |> List.rev 48 | // |> List.toArray 49 | 50 | ////Returns the parameter name and a function to turn a string into the paramter type as tupel 51 | //let private getParsing (headerValue:string) = 52 | // let headerName,headerType = headerValue.Trim().Split ' ' |> fun x -> x.[0],Array.tail x|>String.concat"," 53 | 54 | // if headerType.Trim().Contains "VARCHAR" then headerName,(fun x -> match x with |" "| "" -> (VARCHAR "") |_ -> (VARCHAR x)) 55 | // elif headerType.Trim().Contains "INT" then headerName,(fun x -> match x with |" "| "" -> (INTEGER 0) |_ -> (INTEGER (int x))) 56 | // elif headerType.Trim().Contains "DOUBLE" then headerName,(fun x -> match x with |" "| "" -> (DOUBLE 0.0) |_ -> (DOUBLE (float x))) 57 | // elif headerType.Trim().Contains "BOOLEAN" then headerName,(fun x -> match x with |" "| "" -> (BOOLEAN false) |"true" -> (BOOLEAN true)|"false" -> (BOOLEAN false)|_ -> failwith"unknown value in visible") 58 | // elif headerType.Trim().Contains "node1" ||headerValue.Trim().Contains "node2" then headerName,(fun x -> match x with |" "| "" -> (VARCHAR "") |_ -> (VARCHAR x)) 59 | // else failwith "unknown typeAnnotation in header" 60 | 61 | ////Returns the correct parser given on the given pattern. Used for nodeheader and edgeheader recognition 62 | //let private getParser (s:string) (pattern:string) = 63 | // let regexPattern = $"(?<={pattern}).*" 64 | // let r = System.Text.RegularExpressions.Regex.Match(s,regexPattern) 65 | // if r.Success then 66 | // let parsing = 67 | // r.Value 68 | // |>splitElementInfos 69 | // |> Array.map(fun x -> 70 | // getParsing x) 71 | // Some(parsing) 72 | // else None 73 | 74 | ////Returns the SOME line parsed by the pasingArray. In case of failure return None 75 | //let private tryGetElements (parsingArray:array GDFValue)> option) (GDFItemF: array -> GDFItem) (line:string) = 76 | // try 77 | // line 78 | // |>splitElementInfos 79 | // Array.map2(fun (n,p) l -> n,p l) (Option.get parsingArray) 80 | // |> fun x -> Some (GDFItemF x) 81 | // with _ -> None 82 | 83 | ////Returns the nodeheader if true 84 | //let private (|NodeHeader|_|) (line:string) = 85 | // match line.StartsWith("nodedef>") with 86 | // | true -> Some <| getParser line "nodedef>" 87 | // | false -> None 88 | 89 | ////Returns the edgeHeader if true 90 | //let private (|EdgeHeader|_|) (line:string) = 91 | // match line.StartsWith("edgedef>") with 92 | // | true -> Some <| getParser line "edgedef>" 93 | // | false -> None 94 | 95 | ////Returns a GDFItem.Node if matched and not an Edge 96 | //let private (|Node|_|) (parsingArray:array GDFValue)> option) (isEdge:bool) (line:string) = 97 | // if isEdge then 98 | // None 99 | // else 100 | // tryGetElements parsingArray (fun x -> GDFItem.Node {NodeInfo=x}) line 101 | 102 | ////Returns a GDFItem.Edge if matched and an Edge 103 | //let private (|Edge|_|) (parsingArray:array GDFValue)> option) (isEdge:bool) (line:string) = 104 | // if isEdge then 105 | // tryGetElements parsingArray (fun x -> (GDFItem.Edge {EdgeInfo=x})) line 106 | // else 107 | // None 108 | 109 | ////Parses an seq of lines 110 | //let parser (lines : seq) = 111 | // let en = lines.GetEnumerator() 112 | // let rec loop (parserBuilt: array GDFValue)> option) (isEdge:bool) = 113 | // seq { 114 | // match en.MoveNext() with 115 | // | true -> 116 | 117 | // match en.Current, isEdge with 118 | // | NodeHeader nh, _ -> 119 | // yield! loop (nh) false 120 | // | EdgeHeader eh, _ -> 121 | // yield! loop (eh) true 122 | // | Node parserBuilt isEdge n, false -> 123 | // yield n 124 | // yield! loop parserBuilt isEdge 125 | // | Edge parserBuilt isEdge e, true -> 126 | // yield e 127 | // yield! loop parserBuilt isEdge 128 | // | _,_ -> yield GDFItem.Unknown en.Current 129 | 130 | 131 | // | false -> () 132 | 133 | // } 134 | // loop None false 135 | 136 | -------------------------------------------------------------------------------- /src/Graphoscope/Measures/Modularity.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Measures 2 | 3 | open Graphoscope 4 | open FSharpAux 5 | 6 | type Modularity() = 7 | static member private isValidPartitionDiGraph (partition: 'NodeKey Set []) (graph: DiGraph<'NodeKey, _, 'EdgeData>) = 8 | Set graph.NodeKeys = Set.unionMany partition 9 | 10 | static member private isValidPartitionUndirected (partition: 'NodeKey Set []) (graph: UndirectedGraph<'NodeKey, _, 'EdgeData>) = 11 | Set graph.NodeKeys = Set.unionMany partition 12 | 13 | /// 14 | /// Finds the modularity of a given of the , 15 | /// where is a sequence of Set of nodes that collectively exhaust all the nodes in the. 16 | /// 17 | /// Function to get the edge weight from 'EdgeData. 18 | /// If resolution is less than 1, modularity favors 19 | /// larger communities. Greater than 1 favors smaller communities. 20 | /// A sequence of Set of nodes that collectively exhaust all the nodes in the 21 | /// The graph to analyse 22 | /// Throws if isn't a valid partition of 23 | static member ofUndirectedGraph (getWeight: 'EdgeData -> float) (resolution: float) (partition: 'NodeKey Set []) (graph: UndirectedGraph<'NodeKey, _, 'EdgeData>)= 24 | if Modularity.isValidPartitionUndirected partition graph |> not then 25 | failwith "`partition` is not a valid partition of DiGraph." 26 | let degrees = graph.Edges |> ResizeArray.map(fun x -> (0.,x)||>ResizeArray.fold(fun acc (_, ed) -> acc + getWeight ed)) 27 | let degSum = (0.,degrees)||>ResizeArray.fold(fun acc c -> acc + c) 28 | let m = degSum / 2. 29 | 30 | let normalizer = 1. / degSum**2 31 | 32 | partition 33 | |> Array.sumBy(fun community -> 34 | let commIdxs = community|>Set.map(fun nk -> graph.IdMap[nk]) 35 | let lc = 36 | commIdxs 37 | |> Seq.sumBy(fun nIx -> 38 | (0., graph.Edges[nIx]) 39 | ||> ResizeArray.fold(fun acc (nbrIx,w) -> 40 | if commIdxs |> Set.contains nbrIx then 41 | acc + getWeight w 42 | else acc 43 | ) 44 | ) 45 | |> fun x -> x/2. 46 | 47 | let degreeSum = commIdxs|>Seq.sumBy(fun x -> degrees[x]) 48 | lc / m - resolution * degreeSum * degreeSum * normalizer 49 | ) 50 | 51 | /// 52 | /// Finds the modularity of a given of the , 53 | /// where is a sequence of Set of nodes that collectively exhaust all the nodes in the. 54 | /// 55 | /// Function to get the edge weight from 'EdgeData. 56 | /// If resolution is less than 1, modularity favors 57 | /// larger communities. Greater than 1 favors smaller communities. 58 | /// A sequence of Set of nodes that collectively exhaust all the nodes in the 59 | /// The graph to analyse 60 | /// Throws if isn't a valid partition of 61 | static member ofDiGraph (getWeight: 'EdgeData -> float) (resolution: float) (partition: 'NodeKey Set []) (graph: DiGraph<'NodeKey, _, 'EdgeData>)= 62 | if Modularity.isValidPartitionDiGraph partition graph |> not then 63 | failwith "`partition` is not a valid partition of DiGraph." 64 | 65 | let inDegrees = graph.InEdges |> ResizeArray.map(fun x -> (0.,x)||>ResizeArray.fold(fun acc (_, ed) -> acc + getWeight ed)) 66 | let outDegrees = graph.OutEdges |> ResizeArray.map(fun x -> (0.,x)||>ResizeArray.fold(fun acc (_, ed) -> acc + getWeight ed)) 67 | let m = (0.,inDegrees) ||> ResizeArray.fold(fun acc c -> acc + c) 68 | 69 | let normalizer = 1. / m**2 70 | 71 | partition 72 | |> Array.sumBy(fun community -> 73 | let commIdxs = community|>Set.map(fun nk -> graph.IdMap[nk]) 74 | let lc = 75 | commIdxs 76 | |> Seq.sumBy(fun nIx -> 77 | (0., graph.InEdges[nIx]) 78 | ||> ResizeArray.fold(fun acc (nbrIx,w) -> 79 | if commIdxs|>Set.contains nbrIx then 80 | acc + getWeight w 81 | else acc 82 | ) 83 | ) 84 | let outDegreeSum = commIdxs|>Seq.sumBy(fun x -> outDegrees[x]) 85 | let inDegreeSum = commIdxs|>Seq.sumBy(fun x -> inDegrees[x]) 86 | lc / m - resolution * outDegreeSum * inDegreeSum * normalizer 87 | ) 88 | 89 | /// 90 | /// Finds the modularity of a given of the , 91 | /// where is a sequence of Set of nodes that collectively exhaust all the nodes in the. 92 | /// 93 | /// The graph to analyse 94 | /// A sequence of Set of nodes that collectively exhaust all the nodes in the 95 | /// Function to get the edge weight from 'EdgeData. 96 | /// Optional; defaults to each edge weight being equal to 1.0. 97 | /// 98 | /// If resolution is less than 1, modularity favors 99 | /// larger communities. Greater than 1 favors smaller communities. 100 | /// Optional; default = 1.0 101 | /// Throws if isn't a valid partition of 102 | static member compute (graph: UndirectedGraph<'NodeKey, 'NodeData, 'EdgeData>, partition: 'NodeKey Set [], ?getWeight: 'EdgeData -> float, ?resolution: float) = 103 | let getWeight = defaultArg getWeight (fun _ -> 1.) 104 | let resolution = defaultArg resolution 1. 105 | Modularity.ofUndirectedGraph getWeight resolution partition graph 106 | 107 | /// 108 | /// Finds the modularity of a given of the , 109 | /// where is a sequence of Set of nodes that collectively exhaust all the nodes in the. 110 | /// 111 | /// The graph to analyse 112 | /// A sequence of Set of nodes that collectively exhaust all the nodes in the 113 | /// Function to get the edge weight from 'EdgeData. 114 | /// Optional; defaults to each edge weight being equal to 1.0. 115 | /// 116 | /// If resolution is less than 1, modularity favors 117 | /// larger communities. Greater than 1 favors smaller communities. 118 | /// Optional; default = 1.0 119 | /// Throws if isn't a valid partition of 120 | static member compute (graph: DiGraph<'NodeKey, 'NodeData, 'EdgeData>, partition: 'NodeKey Set [], ?getWeight: 'EdgeData -> float, ?resolution: float) = 121 | let getWeight = defaultArg getWeight (fun _ -> 1.) 122 | let resolution = defaultArg resolution 1. 123 | Modularity.ofDiGraph getWeight resolution partition graph -------------------------------------------------------------------------------- /src/Graphoscope/Algorithms/DFS.fs: -------------------------------------------------------------------------------- 1 | namespace Graphoscope.Algorithms 2 | 3 | open Graphoscope 4 | open System.Collections.Generic 5 | 6 | 7 | 8 | 9 | /// 10 | /// Depth-First Traversal (or Search). 11 | /// 12 | type DFS() = 13 | 14 | static member private searchDiGraph 15 | (starting : 'NodeKey) 16 | (edgeFinder: 'NodeKey -> DiGraph<'NodeKey,'NodeData,'EdgeData> -> ('NodeKey * 'EdgeData) array) 17 | (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 18 | 19 | let visited = HashSet<'NodeKey>() 20 | let stack = Stack<'NodeKey>() 21 | 22 | stack.Push(starting) 23 | visited.Add(starting) |> ignore 24 | 25 | seq { 26 | while stack.Count > 0 do 27 | let nodeKey = stack.Pop() 28 | let sucessors = edgeFinder nodeKey graph 29 | let nodeData = DiGraph.Node.getNodeData nodeKey graph 30 | 31 | yield (nodeKey, nodeData) 32 | 33 | for (key,_) in sucessors do 34 | if not(visited.Contains(key)) then 35 | stack.Push(key) 36 | visited.Add(key) |> ignore 37 | } 38 | 39 | 40 | /// 41 | /// Traverses nodes reachable from given node in a Depth-First Traversal (or Search). 42 | /// 43 | /// Nodekey for starting the DFS traversal. 44 | /// The graph to traverse. 45 | /// Sequence of node key and node data. 46 | static member ofFGraph (starting : 'NodeKey) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 47 | let visited = HashSet<'NodeKey>() 48 | let stack = Stack<'NodeKey>() 49 | 50 | stack.Push(starting) 51 | visited.Add(starting) |> ignore 52 | 53 | seq { 54 | while stack.Count > 0 do 55 | let nodeKey = stack.Pop() 56 | let (_, nd, s) = graph.[nodeKey] 57 | yield (nodeKey, nd) 58 | 59 | for kv in s do 60 | if not(visited.Contains(kv.Key)) then 61 | stack.Push(kv.Key) 62 | visited.Add(kv.Key) |> ignore 63 | } 64 | 65 | 66 | /// 67 | /// Traverses nodes reachable from given node in a Depth-First Traversal (or Search) while using a predicate function. 68 | /// 69 | /// Nodekey for starting the DFS traversal. 70 | /// A function applied to the NodeKeys, NodeData, and EdgeData of a traversed node. Nodes are only collected if the function returns true. 71 | /// The graph to traverse. 72 | /// Sequence of node key and node data traversed where predicate returned true. 73 | /// This function does not collect all traversable nodes via DFS and filters them but instead stops the traversal for a given node as soon as predicate returns false and continues with the next node not yet visited. This means that you may lose subgraphs with nodes that would return true when applied to predicate. 74 | static member ofFGraphBy (starting : 'NodeKey) (predicate : 'NodeKey -> 'NodeData -> 'EdgeData -> bool) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 75 | let visited = HashSet<'NodeKey>() 76 | let stack = Stack<'NodeKey>() 77 | 78 | stack.Push(starting) 79 | visited.Add(starting) |> ignore 80 | 81 | seq { 82 | while stack.Count > 0 do 83 | let nodeKey = stack.Pop() 84 | let (_, nd, s) = graph.[nodeKey] 85 | yield (nodeKey, nd) 86 | 87 | for kv in s do 88 | let _, ndSuccessor, _ = graph[kv.Key] 89 | if not (visited.Contains(kv.Key)) && predicate kv.Key ndSuccessor s[kv.Key] then 90 | stack.Push(kv.Key) 91 | visited.Add(kv.Key) |> ignore 92 | } 93 | 94 | 95 | /// 96 | /// Traverses nodes reachable from given node in a Depth-First Traversal (or Search) with a limited number of follow-up nodes being visited. 97 | /// 98 | /// Nodekey for starting the DFS traversal. 99 | /// The number of follow-up nodes being visited. 100 | /// The graph to traverse. 101 | /// Sequence of node key and node data where predicate returned true. 102 | static member ofFGraphWithDepth (starting : 'NodeKey) depth (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 103 | let visited = HashSet<'NodeKey>() 104 | let stack = Stack<'NodeKey * int>() 105 | 106 | stack.Push(starting,0) 107 | visited.Add(starting) |> ignore 108 | 109 | seq { 110 | while stack.Count > 0 do 111 | let nodeKey, currDepth = stack.Pop() 112 | let (_, nd, s) = graph.[nodeKey] 113 | yield (nodeKey, nd) 114 | 115 | if currDepth < depth then 116 | for kv in s do 117 | if not (visited.Contains(kv.Key)) then 118 | stack.Push(kv.Key, currDepth + 1) 119 | visited.Add(kv.Key) |> ignore 120 | } 121 | 122 | 123 | /// 124 | /// Traverses nodes reachable from given node in a Depth-First Traversal (or Search) with a limited number of follow-up nodes being visited while using a predicate function. 125 | /// 126 | /// Nodekey for starting the DFS traversal. 127 | /// The number of follow-up nodes being visited. 128 | /// A function applied to the NodeKeys, NodeData, and EdgeData of a traversed node. Nodes are only collected if the function returns true. 129 | /// The graph to traverse. 130 | /// Sequence of node key and node data traversed where predicate returned true. 131 | /// This function does not collect all traversable nodes via DFS and filters them but instead stops the traversal for a given node as soon as predicate returns false and continues with the next node not yet visited. This means that you may lose subgraphs with nodes that would return true when applied to predicate. 132 | static member ofFGraphWithDepthBy (starting : 'NodeKey) depth (predicate : 'NodeKey -> 'NodeData -> 'EdgeData -> bool) (graph : FGraph<'NodeKey, 'NodeData, 'EdgeData>) = 133 | let visited = HashSet<'NodeKey>() 134 | let stack = Stack<'NodeKey * int>() 135 | 136 | stack.Push(starting,0) 137 | visited.Add(starting) |> ignore 138 | 139 | seq { 140 | while stack.Count > 0 do 141 | let nodeKey, currDepth = stack.Pop() 142 | let (_, nd, s) = graph.[nodeKey] 143 | yield (nodeKey, nd) 144 | 145 | if currDepth < depth then 146 | for kv in s do 147 | let _, ndSuccessor, _ = graph[kv.Key] 148 | if not( visited.Contains(kv.Key)) && predicate kv.Key ndSuccessor s[kv.Key] then 149 | stack.Push(kv.Key, currDepth + 1) 150 | visited.Add(kv.Key) |> ignore 151 | } 152 | 153 | 154 | /// 155 | /// Traverses nodes reachable from given node in a Depth-First Traversal (or Search). 156 | /// 157 | /// Nodekey for starting the DFS traversal. 158 | /// The graph to traverse. 159 | /// Sequence of node key and node data. 160 | static member ofDiGraph (starting : 'NodeKey) (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 161 | DFS.searchDiGraph starting (DiGraph.getOutEdges) graph 162 | 163 | 164 | /// Same as ofDiGraph except it traverses nodes along in and out edges. 165 | /// This is useful for finding components and other operations where the direction of the edge shouldn't matter. 166 | /// 167 | /// Nodekey for starting the DFS traversal. 168 | /// The graph to traverse. 169 | /// Sequence of node key and node data. 170 | static member ofDiGraphUndirected (starting : 'NodeKey) (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 171 | 172 | let undirectedEdgeFinder (starting : 'NodeKey) (graph : DiGraph<'NodeKey, 'NodeData, 'EdgeData>) = 173 | DiGraph.getOutEdges starting graph 174 | |> Array.append(DiGraph.getInEdges starting graph) 175 | 176 | DFS.searchDiGraph starting undirectedEdgeFinder graph 177 | 178 | 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | *.DS_Store 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.tlog 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Coverlet is a free, cross platform Code Coverage Tool 147 | coverage*.json 148 | coverage*.xml 149 | coverage*.info 150 | 151 | # Visual Studio code coverage results 152 | *.coverage 153 | *.coveragexml 154 | 155 | # NCrunch 156 | _NCrunch_* 157 | .*crunch*.local.xml 158 | nCrunchTemp_* 159 | 160 | # MightyMoose 161 | *.mm.* 162 | AutoTest.Net/ 163 | 164 | # Web workbench (sass) 165 | .sass-cache/ 166 | 167 | # Installshield output folder 168 | [Ee]xpress/ 169 | 170 | # DocProject is a documentation generator add-in 171 | DocProject/buildhelp/ 172 | DocProject/Help/*.HxT 173 | DocProject/Help/*.HxC 174 | DocProject/Help/*.hhc 175 | DocProject/Help/*.hhk 176 | DocProject/Help/*.hhp 177 | DocProject/Help/Html2 178 | DocProject/Help/html 179 | 180 | # Click-Once directory 181 | publish/ 182 | 183 | # Publish Web Output 184 | *.[Pp]ublish.xml 185 | *.azurePubxml 186 | # Note: Comment the next line if you want to checkin your web deploy settings, 187 | # but database connection strings (with potential passwords) will be unencrypted 188 | *.pubxml 189 | *.publishproj 190 | 191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 192 | # checkin your Azure Web App publish settings, but sensitive information contained 193 | # in these scripts will be unencrypted 194 | PublishScripts/ 195 | 196 | # NuGet Packages 197 | *.nupkg 198 | # NuGet Symbol Packages 199 | *.snupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/[Pp]ackages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/[Pp]ackages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/[Pp]ackages/repositories.config 206 | # NuGet v3's project.json files produces more ignorable files 207 | *.nuget.props 208 | *.nuget.targets 209 | 210 | # Microsoft Azure Build Output 211 | csx/ 212 | *.build.csdef 213 | 214 | # Microsoft Azure Emulator 215 | ecf/ 216 | rcf/ 217 | 218 | # Windows Store app package directories and files 219 | AppPackages/ 220 | BundleArtifacts/ 221 | Package.StoreAssociation.xml 222 | _pkginfo.txt 223 | *.appx 224 | *.appxbundle 225 | *.appxupload 226 | 227 | # Visual Studio cache files 228 | # files ending in .cache can be ignored 229 | *.[Cc]ache 230 | # but keep track of directories ending in .cache 231 | !?*.[Cc]ache/ 232 | 233 | # Others 234 | ClientBin/ 235 | ~$* 236 | *~ 237 | *.dbmdl 238 | *.dbproj.schemaview 239 | *.jfm 240 | *.pfx 241 | *.publishsettings 242 | orleans.codegen.cs 243 | 244 | # Including strong name files can present a security risk 245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 246 | #*.snk 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | ServiceFabricBackup/ 263 | *.rptproj.bak 264 | 265 | # SQL Server files 266 | *.mdf 267 | *.ldf 268 | *.ndf 269 | 270 | # Business Intelligence projects 271 | *.rdl.data 272 | *.bim.layout 273 | *.bim_*.settings 274 | *.rptproj.rsuser 275 | *- [Bb]ackup.rdl 276 | *- [Bb]ackup ([0-9]).rdl 277 | *- [Bb]ackup ([0-9][0-9]).rdl 278 | 279 | # Microsoft Fakes 280 | FakesAssemblies/ 281 | 282 | # GhostDoc plugin setting file 283 | *.GhostDoc.xml 284 | 285 | # Node.js Tools for Visual Studio 286 | .ntvs_analysis.dat 287 | node_modules/ 288 | 289 | # Visual Studio 6 build log 290 | *.plg 291 | 292 | # Visual Studio 6 workspace options file 293 | *.opt 294 | 295 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 296 | *.vbw 297 | 298 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 299 | *.vbp 300 | 301 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 302 | *.dsw 303 | *.dsp 304 | 305 | # Visual Studio 6 technical files 306 | *.ncb 307 | *.aps 308 | 309 | # Visual Studio LightSwitch build output 310 | **/*.HTMLClient/GeneratedArtifacts 311 | **/*.DesktopClient/GeneratedArtifacts 312 | **/*.DesktopClient/ModelManifest.xml 313 | **/*.Server/GeneratedArtifacts 314 | **/*.Server/ModelManifest.xml 315 | _Pvt_Extensions 316 | 317 | # Paket dependency manager 318 | .paket/paket.exe 319 | paket-files/ 320 | 321 | # FAKE - F# Make 322 | .fake/ 323 | 324 | # CodeRush personal settings 325 | .cr/personal 326 | 327 | # Python Tools for Visual Studio (PTVS) 328 | __pycache__/ 329 | *.pyc 330 | 331 | # Cake - Uncomment if you are using it 332 | # tools/** 333 | # !tools/packages.config 334 | 335 | # Tabs Studio 336 | *.tss 337 | 338 | # Telerik's JustMock configuration file 339 | *.jmconfig 340 | 341 | # BizTalk build output 342 | *.btp.cs 343 | *.btm.cs 344 | *.odx.cs 345 | *.xsd.cs 346 | 347 | # OpenCover UI analysis results 348 | OpenCover/ 349 | 350 | # Azure Stream Analytics local run output 351 | ASALocalRun/ 352 | 353 | # MSBuild Binary and Structured Log 354 | *.binlog 355 | 356 | # NVidia Nsight GPU debugger configuration file 357 | *.nvuser 358 | 359 | # MFractors (Xamarin productivity tool) working folder 360 | .mfractor/ 361 | 362 | # Local History for Visual Studio 363 | .localhistory/ 364 | 365 | # Visual Studio History (VSHistory) files 366 | .vshistory/ 367 | 368 | # BeatPulse healthcheck temp database 369 | healthchecksdb 370 | 371 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 372 | MigrationBackup/ 373 | 374 | # Ionide (cross platform F# VS Code tools) working folder 375 | .ionide/ 376 | 377 | # Fody - auto-generated XML schema 378 | FodyWeavers.xsd 379 | 380 | # VS Code files for those working on multiple tools 381 | .vscode/* 382 | !.vscode/settings.json 383 | !.vscode/tasks.json 384 | !.vscode/launch.json 385 | !.vscode/extensions.json 386 | *.code-workspace 387 | 388 | # Local History for Visual Studio Code 389 | .history/ 390 | 391 | # Windows Installer files from build outputs 392 | *.cab 393 | *.msi 394 | *.msix 395 | *.msm 396 | *.msp 397 | 398 | # JetBrains Rider 399 | *.sln.iml 400 | /tmp/watch 401 | /.fsdocs 402 | --------------------------------------------------------------------------------