├── test ├── testdata │ ├── graph-50-500.jgz │ ├── tutte-pathdigraph.jgz │ ├── graph-decomposition.jgz │ ├── graph-50-500-sc.txt │ ├── graph-50-500-rc.txt │ └── graph-50-500-bc.txt ├── experimental │ └── experimental.jl ├── centrality │ ├── katz.jl │ ├── degree.jl │ ├── eigenvector.jl │ ├── radiality.jl │ ├── stress.jl │ ├── pagerank.jl │ └── closeness.jl ├── linalg │ └── runtests.jl ├── community │ ├── clustering.jl │ ├── clique_percolation.jl │ ├── label_propagation.jl │ ├── core-periphery.jl │ └── cliques.jl ├── shortestpaths │ ├── astar.jl │ ├── johnson.jl │ ├── floyd-warshall.jl │ └── bellman-ford.jl ├── parallel │ ├── utils.jl │ ├── traversals │ │ ├── gdistances.jl │ │ ├── greedy_color.jl │ │ └── bfs.jl │ ├── runtests.jl │ ├── centrality │ │ ├── radiality.jl │ │ ├── stress.jl │ │ └── pagerank.jl │ ├── shortestpaths │ │ ├── johnson.jl │ │ ├── floyd-warshall.jl │ │ └── bellman-ford.jl │ ├── distance.jl │ ├── dominatingset │ │ └── minimal_dom_set.jl │ ├── vertexcover │ │ └── random_vertex_cover.jl │ └── independentset │ │ └── maximal_ind_set.jl ├── vertexcover │ ├── degree_vertex_cover.jl │ └── random_vertex_cover.jl ├── traversals │ ├── dfs.jl │ ├── greedy_color.jl │ ├── maxadjvisit.jl │ └── bipartition.jl ├── simplegraphs │ ├── simpleedge.jl │ └── generators │ │ ├── binomial.jl │ │ ├── euclideangraphs.jl │ │ └── smallgraphs.jl ├── graphcut │ └── karger_min_cut.jl ├── deprecations.jl ├── biconnectivity │ ├── articulation.jl │ ├── bridge.jl │ └── biconnect.jl ├── spanningtrees │ ├── prim.jl │ └── kruskal.jl ├── dominatingset │ ├── degree_dom_set.jl │ └── minimal_dom_set.jl ├── independentset │ ├── degree_ind_set.jl │ └── maximal_ind_set.jl ├── degeneracy.jl ├── interface.jl ├── edit_distance.jl ├── steinertree │ └── steiner_tree.jl ├── persistence │ └── persistence.jl ├── cycles │ ├── basis.jl │ ├── limited_length.jl │ ├── hawick-james.jl │ └── johnson.jl └── utils.jl ├── docs ├── Project.toml ├── src │ ├── matching.md │ ├── core.md │ ├── degeneracy.md │ ├── distance.md │ ├── citing.md │ ├── linalg.md │ ├── operators.md │ ├── coloring.md │ ├── community.md │ ├── centrality.md │ ├── integration.md │ ├── experimental.md │ ├── graphtypes.md │ ├── persistence.md │ ├── developing.md │ ├── errorhandling.md │ ├── generators.md │ ├── index.md │ ├── basicproperties.md │ ├── plotting.md │ └── parallel.md └── make.jl ├── benchmark ├── insertions.jl ├── connectivity.jl ├── traversals.jl ├── edges.jl ├── centrality.jl ├── core.jl ├── benchmarks.jl └── parallel │ └── egonets.jl ├── .gitignore ├── CITATION.bib ├── codecov.yml ├── src ├── deprecations.jl ├── Parallel │ ├── traversals │ │ └── greedy_color.jl │ ├── vertexcover │ │ └── random_vertex_cover.jl │ ├── independentset │ │ └── maximal_ind_set.jl │ ├── dominatingset │ │ └── minimal_dom_set.jl │ ├── distance.jl │ ├── Parallel.jl │ ├── shortestpaths │ │ ├── johnson.jl │ │ ├── dijkstra.jl │ │ ├── floyd-warshall.jl │ │ └── bellman-ford.jl │ ├── centrality │ │ ├── radiality.jl │ │ ├── stress.jl │ │ ├── pagerank.jl │ │ └── closeness.jl │ └── utils.jl ├── Experimental │ ├── Experimental.jl │ └── ShortestPaths │ │ ├── desopo-pape.jl │ │ ├── bellman-ford.jl │ │ └── bfs.jl ├── community │ ├── core-periphery.jl │ └── clique_percolation.jl ├── centrality │ ├── eigenvector.jl │ ├── radiality.jl │ ├── degree.jl │ ├── closeness.jl │ ├── katz.jl │ ├── pagerank.jl │ └── stress.jl ├── spanningtrees │ ├── prim.jl │ └── kruskal.jl ├── SimpleGraphs │ ├── simpleedge.jl │ └── generators │ │ └── deprecations.jl ├── independentset │ ├── maximal_ind_set.jl │ └── degree_ind_set.jl ├── vertexcover │ ├── random_vertex_cover.jl │ └── degree_vertex_cover.jl ├── dominatingset │ ├── minimal_dom_set.jl │ └── degree_dom_set.jl ├── linalg │ └── LinAlg.jl ├── cycles │ ├── limited_length.jl │ ├── basis.jl │ └── hawick-james.jl ├── graphcut │ └── karger_min_cut.jl ├── shortestpaths │ └── desopo-pape.jl ├── traversals │ └── bipartition.jl └── biconnectivity │ └── articulation.jl ├── .github ├── stale.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── .zenodo.json ├── Project.toml ├── .travis.yml └── appveyor.yml /test/testdata/graph-50-500.jgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/LightGraphs.jl/master/test/testdata/graph-50-500.jgz -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 3 | 4 | [compat] 5 | Documenter = "~0.23.4" 6 | -------------------------------------------------------------------------------- /docs/src/matching.md: -------------------------------------------------------------------------------- 1 | # Matching 2 | 3 | Maximum weight matching is supported in the companion package *LightGraphsExtras.jl*. 4 | -------------------------------------------------------------------------------- /test/testdata/tutte-pathdigraph.jgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/LightGraphs.jl/master/test/testdata/tutte-pathdigraph.jgz -------------------------------------------------------------------------------- /test/testdata/graph-decomposition.jgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/LightGraphs.jl/master/test/testdata/graph-decomposition.jgz -------------------------------------------------------------------------------- /benchmark/insertions.jl: -------------------------------------------------------------------------------- 1 | @benchgroup "insertions" begin 2 | n = 10000 3 | @bench "ER Generation" g = SimpleGraph($n, 16 * $n) 4 | end 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.jl.cov 3 | *.jl.mem 4 | .vscode 5 | docs/build/ 6 | docs/site/ 7 | benchmark/.results/* 8 | benchmark/.tune.jld 9 | *.cov 10 | Manifest.toml 11 | -------------------------------------------------------------------------------- /docs/src/core.md: -------------------------------------------------------------------------------- 1 | # Core Functions 2 | *LightGraphs.jl* includes the following core functions: 3 | 4 | ```@index 5 | Order = [:type, :function] 6 | Pages = ["core.md"] 7 | ``` 8 | 9 | ## Full Docs 10 | 11 | ```@autodocs 12 | Modules = [LightGraphs] 13 | Pages = [ "core.jl"] 14 | Private = false 15 | ``` 16 | -------------------------------------------------------------------------------- /test/experimental/experimental.jl: -------------------------------------------------------------------------------- 1 | const exptestdir = dirname(@__FILE__) 2 | tests = ["isomorphism", "shortestpaths"] 3 | 4 | @testset "Experimental" begin 5 | @test length(description()) > 1 6 | 7 | for t in tests 8 | tp = joinpath(exptestdir, "$(t).jl") 9 | include(tp) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/centrality/katz.jl: -------------------------------------------------------------------------------- 1 | @testset "Katz" begin 2 | g5 = SimpleDiGraph(4) 3 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 4 | for g in testdigraphs(g5) 5 | z = @inferred(katz_centrality(g, 0.4)) 6 | @test round.(z, digits=2) == [0.32, 0.44, 0.62, 0.56] 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /docs/src/degeneracy.md: -------------------------------------------------------------------------------- 1 | # Graph Decomposition 2 | 3 | *LightGraphs.jl* provides the following graph degeneracy functions: 4 | 5 | ```@index 6 | Order = [:type, :function] 7 | Pages = ["degeneracy.md"] 8 | ``` 9 | 10 | ## Full Docs 11 | 12 | ```@autodocs 13 | Modules = [LightGraphs] 14 | Pages = ["degeneracy.jl"] 15 | Private = false 16 | ``` 17 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @misc{Bromberger17, 2 | author = {Seth Bromberger, James Fairbanks, and other contributors}, 3 | title = {JuliaGraphs/LightGraphs.jl: an optimized graphs package for the Julia programming language}, 4 | year = 2017, 5 | doi = {10.5281/zenodo.889971}, 6 | url = {https://doi.org/10.5281/zenodo.889971} 7 | } 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | target: '97' 6 | project: 7 | default: 8 | target: '90' 9 | comment: 10 | layout: "header, diff" 11 | behavior: default 12 | require_changes: false # if true: only post the comment if coverage changes 13 | branches: null 14 | flags: null 15 | paths: null 16 | -------------------------------------------------------------------------------- /docs/src/distance.md: -------------------------------------------------------------------------------- 1 | # Distance 2 | *LightGraphs.jl* includes the following distance measurements: 3 | 4 | ```@index 5 | Order = [:type, :function] 6 | Pages = ["distance.md"] 7 | ``` 8 | 9 | ## Full Docs 10 | 11 | ```@autodocs 12 | Modules = [LightGraphs] 13 | Pages = [ 14 | "distance.jl", 15 | "transitivity.jl" 16 | ] 17 | Private = false 18 | ``` 19 | -------------------------------------------------------------------------------- /src/deprecations.jl: -------------------------------------------------------------------------------- 1 | # deprecations should also include a comment stating the version 2 | # for which the deprecation should be removed. 3 | 4 | # Deprecated to fix spelling. Can be removed for version 2.0. 5 | @deprecate simplecycles_hadwick_james simplecycles_hawick_james 6 | 7 | # Deprecated for more explicit function name. Can be removed for version 2.0. 8 | @deprecate saw self_avoiding_walk 9 | -------------------------------------------------------------------------------- /test/linalg/runtests.jl: -------------------------------------------------------------------------------- 1 | using LightGraphs.LinAlg 2 | using Random 3 | using SparseArrays 4 | using LinearAlgebra 5 | const linalgtestdir = dirname(@__FILE__) 6 | 7 | tests = [ 8 | "graphmatrices", 9 | "spectral" 10 | ] 11 | 12 | 13 | @testset "LightGraphs.LinAlg" begin 14 | for t in tests 15 | tp = joinpath(linalgtestdir, "$(t).jl") 16 | include(tp) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docs/src/citing.md: -------------------------------------------------------------------------------- 1 | We encourage you to cite our work if you have used our libraries, tools or datasets. Starring the repository on GitHub is also appreciated. 2 | 3 | The latest citation information may be found in the [CITATION.bib](https://raw.githubusercontent.com/JuliaGraphs/LightGraphs.jl/master/CITATION.bib) file 4 | within the LightGraphs repository. 5 | 6 | For previous versions, [please reference the zenodo site](https://zenodo.org/record/889971). 7 | -------------------------------------------------------------------------------- /test/testdata/graph-50-500-sc.txt: -------------------------------------------------------------------------------- 1 | 136 2 | 299 3 | 369 4 | 133 5 | 230 6 | 288 7 | 173 8 | 301 9 | 265 10 | 199 11 | 241 12 | 67 13 | 159 14 | 172 15 | 242 16 | 296 17 | 480 18 | 188 19 | 143 20 | 256 21 | 143 22 | 134 23 | 211 24 | 222 25 | 173 26 | 131 27 | 151 28 | 152 29 | 256 30 | 197 31 | 110 32 | 213 33 | 125 34 | 122 35 | 104 36 | 318 37 | 347 38 | 257 39 | 175 40 | 256 41 | 453 42 | 260 43 | 140 44 | 66 45 | 123 46 | 296 47 | 200 48 | 174 49 | 349 50 | 137 51 | -------------------------------------------------------------------------------- /benchmark/connectivity.jl: -------------------------------------------------------------------------------- 1 | @benchgroup "connectivity" begin 2 | @benchgroup "digraphs" begin 3 | for (name, g) in DIGRAPHS 4 | @bench "$(name): strongly_connected_components" LightGraphs.strongly_connected_components($g) 5 | end 6 | end # digraphs 7 | @benchgroup "graphs" begin 8 | for (name, g) in GRAPHS 9 | @bench "$(name): connected_components" LightGraphs.connected_components($g) 10 | end 11 | end # graphs 12 | end # connectivity 13 | -------------------------------------------------------------------------------- /docs/src/linalg.md: -------------------------------------------------------------------------------- 1 | # Linear Algebra 2 | 3 | *LightGraphs.jl* provides the following matrix operations on both directed and undirected graphs in the `LinAlg` submodule: 4 | 5 | ```@index 6 | Order = [:type, :function] 7 | Pages = ["linalg.md"] 8 | ``` 9 | 10 | ## Full Docs 11 | 12 | ```@autodocs 13 | Modules = [LightGraphs.LinAlg] 14 | Pages = [ 15 | "graphmatrices.jl", 16 | "Nonbacktracking.jl", 17 | "spectral.jl" 18 | ] 19 | Private = false 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/src/operators.md: -------------------------------------------------------------------------------- 1 | # Operators 2 | 3 | *LightGraphs.jl* implements the following graph operators. In general, 4 | functions with two graph arguments will require them to be of the same type 5 | (either both `SimpleGraph` or both `SimpleDiGraph`). 6 | 7 | ```@index 8 | Order = [:type, :function] 9 | Pages = ["operators.md"] 10 | ``` 11 | 12 | ## Full Docs 13 | 14 | ```@autodocs 15 | Modules = [LightGraphs] 16 | Pages = ["operators.jl"] 17 | Private = false 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/coloring.md: -------------------------------------------------------------------------------- 1 | # Coloring 2 | 3 | *LightGraphs.jl* defines a structure and basic interface for coloring algorithms. 4 | Since coloring is a hard problem in the general case, users can extend the behavior 5 | and define their own function taking a graph as input and returning the `Coloring` structure. 6 | 7 | ```@index 8 | Order = [:type, :function] 9 | Pages = ["coloring.md"] 10 | ``` 11 | 12 | ```@autodocs 13 | Modules = [LightGraphs] 14 | Pages = [ 15 | "traversals/greedy_color.jl", 16 | ] 17 | ``` 18 | -------------------------------------------------------------------------------- /test/centrality/degree.jl: -------------------------------------------------------------------------------- 1 | @testset "Degree" begin 2 | g5 = SimpleDiGraph(4) 3 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 4 | for g in testdigraphs(g5) 5 | @test @inferred(degree_centrality(g)) == [0.6666666666666666, 0.6666666666666666, 1.0, 0.3333333333333333] 6 | @test @inferred(indegree_centrality(g, normalize=false)) == [0.0, 1.0, 2.0, 1.0] 7 | @test @inferred(outdegree_centrality(g; normalize=false)) == [2.0, 1.0, 1.0, 0.0] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /benchmark/traversals.jl: -------------------------------------------------------------------------------- 1 | @benchgroup "traversals" begin 2 | @benchgroup "graphs" begin 3 | for (name, g) in GRAPHS 4 | @bench "$(name): bfs_tree" LightGraphs.bfs_tree($g, 1) 5 | @bench "$(name): dfs_tree" LightGraphs.dfs_tree($g, 1) 6 | end 7 | end # graphs 8 | @benchgroup "digraphs" begin 9 | for (name, g) in DIGRAPHS 10 | @bench "$(name): bfs_tree" LightGraphs.bfs_tree($g, 1) 11 | @bench "$(name): dfs_tree" LightGraphs.dfs_tree($g, 1) 12 | end 13 | end # digraphs 14 | end # traversals 15 | -------------------------------------------------------------------------------- /test/community/clustering.jl: -------------------------------------------------------------------------------- 1 | @testset "Clustering" begin 2 | g10 = complete_graph(10) 3 | for g in testgraphs(g10) 4 | @test @inferred(local_clustering_coefficient(g, 1)) == 1.0 5 | @test @inferred(local_clustering_coefficient(g)) == ones(10) 6 | @test @inferred(global_clustering_coefficient(g)) == 1.0 7 | @test @inferred(local_clustering(g)) == (fill(36, 10), fill(36, 10)) 8 | @test @inferred(triangles(g)) == fill(36, 10) 9 | @test @inferred(triangles(g, 1)) == 36 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/Parallel/traversals/greedy_color.jl: -------------------------------------------------------------------------------- 1 | function random_greedy_color(g::AbstractGraph{T}, reps::Integer) where {T <: Integer} 2 | best = @distributed (LightGraphs.best_color) for i in 1:reps 3 | seq = shuffle(vertices(g)) 4 | LightGraphs.perm_greedy_color(g, seq) 5 | end 6 | 7 | return convert(LightGraphs.Coloring{T}, best) 8 | end 9 | 10 | greedy_color(g::AbstractGraph{U}; sort_degree::Bool=false, reps::Integer=1) where {U <: Integer} = 11 | sort_degree ? LightGraphs.degree_greedy_color(g) : Parallel.random_greedy_color(g, reps) 12 | -------------------------------------------------------------------------------- /docs/src/community.md: -------------------------------------------------------------------------------- 1 | # Community Structures 2 | 3 | *LightGraphs.jl* contains many algorithm to detect and analyze community structures 4 | in graphs. These include: 5 | 6 | ```@index 7 | Order = [:type, :function] 8 | Pages = ["community.md"] 9 | ``` 10 | 11 | ## Full Docs 12 | 13 | ```@autodocs 14 | Modules = [LightGraphs] 15 | Pages = [ 16 | "community/cliques.jl", 17 | "community/clustering.jl", 18 | "community/core-periphery.jl", 19 | "community/label_propagation.jl", 20 | "community/modularity.jl" 21 | ] 22 | Private = false 23 | ``` 24 | -------------------------------------------------------------------------------- /test/shortestpaths/astar.jl: -------------------------------------------------------------------------------- 1 | @testset "A star" begin 2 | g3 = path_graph(5) 3 | g4 = path_digraph(5) 4 | 5 | d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) 6 | d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) 7 | for g in testgraphs(g3), dg in testdigraphs(g4) 8 | @test @inferred(a_star(g, 1, 4, d1)) == 9 | @inferred(a_star(dg, 1, 4, d1)) == 10 | @inferred(a_star(g, 1, 4, d2)) 11 | @test isempty(@inferred(a_star(dg, 4, 1))) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/parallel/utils.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Generate Reduce" begin 2 | 3 | function make_vec(g::AbstractGraph{T}) where T<:Integer 4 | return Vector{T}(undef, nv(g)) 5 | end 6 | 7 | function comp_vec(x::Vector, y::Vector) 8 | return length(x) < length(y) 9 | end 10 | 11 | g1 = star_graph(5) 12 | 13 | for parallel in [:distributed, :threads] 14 | for g in testgraphs(g1) 15 | 16 | s = @inferred(LightGraphs.Parallel.generate_reduce(g, make_vec, comp_vec, 5; parallel=parallel)) 17 | @test length(s) == 5 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/centrality/eigenvector.jl: -------------------------------------------------------------------------------- 1 | @testset "Eigenvector" begin 2 | g1 = smallgraph(:house) 3 | g2 = cycle_digraph(4) 4 | 5 | for g in testgraphs(g1) 6 | y = @inferred(eigenvector_centrality(g)) 7 | @test round.(y, digits=3) == round.([ 8 | 0.3577513877490464, 0.3577513877490464, 0.5298987782873977, 9 | 0.5298987782873977, 0.4271328349194304 10 | ], digits=3) 11 | end 12 | for g in testdigraphs(g2) 13 | y = @inferred(eigenvector_centrality(g)) 14 | @test round.(y, digits=3) == round.([0.5, 0.5, 0.5, 0.5], digits=3) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/centrality/radiality.jl: -------------------------------------------------------------------------------- 1 | @testset "Radiality" begin 2 | gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") 3 | 4 | c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-rc.txt"), ',')) 5 | for g in testdigraphs(gint) 6 | z = @inferred(radiality_centrality(g)) 7 | @test z == c 8 | end 9 | 10 | g1 = cycle_graph(4) 11 | add_vertex!(g1) 12 | add_edge!(g1, 4, 5) 13 | for g in testgraphs(g1) 14 | z = @inferred(radiality_centrality(g)) 15 | @test z ≈ [5 // 6, 3 // 4, 5 // 6, 11 // 12, 2 // 3] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/community/clique_percolation.jl: -------------------------------------------------------------------------------- 1 | @testset "Clique percolation" begin 2 | function setofsets(array_of_arrays) 3 | Set(map(BitSet, array_of_arrays)) 4 | end 5 | 6 | function test_cliques(graph, expected) 7 | # Make test results insensitive to ordering 8 | Set(clique_percolation(graph)) == setofsets(expected) 9 | end 10 | 11 | g = Graph(5) 12 | add_edge!(g, 1, 2) 13 | add_edge!(g, 2, 3) 14 | add_edge!(g, 3, 1) 15 | add_edge!(g, 1, 4) 16 | add_edge!(g, 4, 5) 17 | add_edge!(g, 5, 1) 18 | @test test_cliques(g, Array[[1, 2, 3], [1, 4, 5]]) 19 | end 20 | -------------------------------------------------------------------------------- /test/community/label_propagation.jl: -------------------------------------------------------------------------------- 1 | @testset "Label propagation" begin 2 | n = 10 3 | g10 = complete_graph(n) 4 | for g in testgraphs(g10) 5 | z = copy(g) 6 | for k = 2:5 7 | z = blockdiag(z, g) 8 | add_edge!(z, (k - 1) * n, k * n) 9 | c, ch = @inferred(label_propagation(z)) 10 | a = collect(n:n:(k * n)) 11 | a = Int[div(i - 1, n) + 1 for i = 1:(k * n)] 12 | # check the number of communities 13 | @test length(unique(a)) == length(unique(c)) 14 | # check the partition 15 | @test a == c 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/parallel/traversals/gdistances.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.BFS" begin 2 | 3 | 4 | g5 = SimpleDiGraph(4) 5 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 6 | 7 | for g in testdigraphs(g5) 8 | @test @inferred(Parallel.gdistances(g, 1)) == gdistances(g, 1) 9 | @test @inferred(Parallel.gdistances(g, [1, 3])) == gdistances(g, [1, 3]) 10 | end 11 | 12 | g6 = smallgraph(:house) 13 | 14 | for g in testgraphs(g6) 15 | @test @inferred(Parallel.gdistances(g, 2)) == gdistances(g, 2) 16 | @test @inferred(Parallel.gdistances(g, [1, 2])) == gdistances(g, [1, 2]) 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /test/vertexcover/degree_vertex_cover.jl: -------------------------------------------------------------------------------- 1 | @testset "Degree Vertex Cover" begin 2 | 3 | g3 = star_graph(5) 4 | for g in testgraphs(g3) 5 | c = @inferred(vertex_cover(g, DegreeVertexCover())) 6 | @test c == [1,] 7 | end 8 | 9 | g4 = complete_graph(5) 10 | for g in testgraphs(g4) 11 | c = @inferred(vertex_cover(g, DegreeVertexCover())) 12 | @test length(c)== 4 #All except one vertex 13 | end 14 | 15 | #path_graph(5) with additional edge 2-5 16 | g5 = Graph([0 1 0 0 0; 1 0 1 0 1; 0 1 0 1 0; 0 0 1 0 1; 0 1 0 1 0]) 17 | for g in testgraphs(g5) 18 | c = @inferred(vertex_cover(g, DegreeVertexCover())) 19 | @test sort(c) == [2, 4] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/community/core-periphery.jl: -------------------------------------------------------------------------------- 1 | @testset "Core periphery" begin 2 | g10 = star_graph(10) 3 | for g in testgraphs(g10) 4 | c = core_periphery_deg(g) 5 | @test @inferred(degree(g, 1)) == 9 6 | @test c[1] == 1 7 | for i = 2:10 8 | @test c[i] == 2 9 | end 10 | end 11 | 12 | g10 = star_graph(10) 13 | g10 = blockdiag(g10, g10) 14 | add_edge!(g10, 1, 11) 15 | for g in testgraphs(g10) 16 | c = @inferred(core_periphery_deg(g)) 17 | @test c[1] == 1 18 | @test c[11] == 1 19 | for i = 2:10 20 | @test c[i] == 2 21 | end 22 | for i = 12:20 23 | @test c[i] == 2 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/src/centrality.md: -------------------------------------------------------------------------------- 1 | # Centrality Measures 2 | 3 | [Centrality measures](https://en.wikipedia.org/wiki/Centrality) describe the 4 | importance of a vertex to the rest of the graph using some set of criteria. 5 | Centrality measures implemented in *LightGraphs.jl* include the following: 6 | 7 | 8 | ```@index 9 | Pages = ["centrality.md"] 10 | ``` 11 | 12 | ## Full docs 13 | 14 | ```@autodocs 15 | Modules = [LightGraphs] 16 | Pages = [ 17 | "centrality/betweenness.jl", 18 | "centrality/closeness.jl", 19 | "centrality/degree.jl", 20 | "centrality/eigenvector.jl", 21 | "centrality/katz.jl", 22 | "centrality/pagerank.jl", 23 | "centrality/stress.jl", 24 | "centrality/radiality.jl" 25 | ] 26 | Private = false 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /src/Experimental/Experimental.jl: -------------------------------------------------------------------------------- 1 | module Experimental 2 | 3 | using LightGraphs 4 | using LightGraphs.SimpleGraphs 5 | 6 | export description, 7 | #isomorphism 8 | VF2, vf2, IsomorphismProblem, SubgraphIsomorphismProblem, InducedSubgraphIsomorphismProblem, 9 | could_have_isomorph, has_isomorph, all_isomorph, count_isomorph, 10 | has_induced_subgraphisomorph, count_induced_subgraphisomorph, all_induced_subgraphisomorph, 11 | has_subgraphisomorph, count_subgraphisomorph, all_subgraphisomorph, 12 | 13 | ShortestPaths 14 | description() = "This module contains experimental graph functions." 15 | 16 | include("isomorphism.jl") 17 | include("vf2.jl") # Julian implementation of VF2 algorithm 18 | include("ShortestPaths/ShortestPaths.jl") 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/centrality/stress.jl: -------------------------------------------------------------------------------- 1 | @testset "Stress" begin 2 | gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") 3 | 4 | c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-sc.txt"), ',')) 5 | for g in testdigraphs(gint) 6 | z = @inferred(stress_centrality(g)) 7 | @test z == c 8 | 9 | x = @inferred(stress_centrality(g, 3)) 10 | x2 = @inferred(stress_centrality(g, collect(1:20))) 11 | @test length(x) == 50 12 | @test length(x2) == 50 13 | end 14 | 15 | g1 = cycle_graph(4) 16 | add_vertex!(g1) 17 | add_edge!(g1, 4, 5) 18 | for g in testgraphs(g1) 19 | z = @inferred(stress_centrality(g)) 20 | @test z == [4, 2, 4, 10, 0] 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/Parallel/vertexcover/random_vertex_cover.jl: -------------------------------------------------------------------------------- 1 | """ 2 | vertex_cover(g, reps, RandomVertexCover(); parallel=:threads) 3 | 4 | Perform [`LightGraphs.vertex_cover(g, RandomVertexCover())`](@ref) `reps` times in parallel 5 | and return the solution with the fewest vertices. 6 | 7 | ### Optional Arguements 8 | - `parallel=:threads`: If `parallel=:distributed` then the multiprocessor implementation is 9 | used. This implementation is more efficient if `reps` is large. 10 | """ 11 | vertex_cover(g::AbstractGraph{T}, reps::Integer, alg::RandomVertexCover; parallel=:threads) where T <: Integer = 12 | LightGraphs.Parallel.generate_reduce(g, (g::AbstractGraph{T})->LightGraphs.vertex_cover(g, alg), 13 | (x::Vector{T}, y::Vector{T})->length(x)LightGraphs.independent_set(g, alg), 13 | (x::Vector{T}, y::Vector{T})->length(x)>length(y), reps; parallel=parallel) 14 | -------------------------------------------------------------------------------- /docs/src/integration.md: -------------------------------------------------------------------------------- 1 | # Integration with other packages 2 | 3 | *LightGraphs.jl*'s integration with other Julia packages is designed to be straightforward. Here are a few examples. 4 | 5 | ## [Graphs.jl](http://github.com/JuliaLang/Graphs.jl) 6 | 7 | Creating a Graphs.jl `simple_graph` is easy: 8 | 9 | ```julia 10 | julia> s = simple_graph(nv(g), is_directed=LightGraphs.is_directed(g)) 11 | julia> for e in LightGraphs.edges(g) 12 | add_edge!(s,src(e), dst(e)) 13 | end 14 | ``` 15 | 16 | ## [Metis.jl](https://github.com/JuliaSparse/Metis.jl) 17 | 18 | The Metis graph partitioning package can interface with *LightGraphs.jl*: 19 | 20 | ```julia 21 | julia> using LightGraphs 22 | 23 | julia> g = SimpleGraph(100,1000) 24 | {100, 1000} undirected graph 25 | 26 | julia> partGraphKway(g, 6) # 6 partitions 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug with LightGraphs 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description of bug** 11 | Describe the bug clearly and concisely. 12 | 13 | 14 | **How to reproduce** 15 | Detail steps to reproduce the behavior. 16 | 17 | 18 | **Expected behavior** 19 | Describe what you expected to happen. 20 | 21 | 22 | **Actual behavior** 23 | Describe what actually happened. 24 | 25 | 26 | **Code demonstrating bug** 27 | If applicable, provide a minimal working example of the bug. 28 | 29 | 30 | **Version information** 31 | - output from `versioninfo()` surrounded by backticks (``) 32 | - output from `] status LightGraphs` surrounded by backticks (``) 33 | 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "LightGraphs: An Optimized Graphs Package for the Julia Programming Language", 3 | "license": "BSD-2-Clause", 4 | "title": "JuliaGraphs/LightGraphs.jl", 5 | "upload_type": "software", 6 | "publication_date": "2017-09-16", 7 | "creators": [ 8 | { 9 | "name": "Seth Bromberger" 10 | }, 11 | { 12 | "name": "other contributors" 13 | }], 14 | "access_right": "open", 15 | "related_identifiers": [ 16 | { 17 | "scheme": "url", 18 | "identifier": "https://github.com/JuliaGraphs/LightGraphs.jl/tree/v0.10.5", 19 | "relation": "isSupplementTo" 20 | }, 21 | { 22 | "scheme": "doi", 23 | "identifier": "10.5281/zenodo.889971", 24 | "relation": "isPartOf" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /benchmark/edges.jl: -------------------------------------------------------------------------------- 1 | import Base: convert 2 | 3 | const P = Pair{Int,Int} 4 | 5 | convert(::Type{Tuple}, e::Pair) = (e.first, e.second) 6 | 7 | function fille(n) 8 | t = Array{LightGraphs.Edge,1}(n) 9 | for i in 1:n 10 | t[i] = LightGraphs.Edge(i, i + 1) 11 | end 12 | return t 13 | end 14 | 15 | function fillp(n) 16 | t = Array{P,1}(n) 17 | for i in 1:n 18 | t[i] = P(i, i + 1) 19 | end 20 | return t 21 | end 22 | 23 | function tsum(t) 24 | x = 0 25 | for i in 1:length(t) 26 | u, v = Tuple(t[i]) 27 | x += u 28 | x += v 29 | end 30 | return x 31 | end 32 | 33 | n = 10000 34 | @benchgroup "edges" begin 35 | @bench "$(n): fille" fille($n) 36 | @bench "$(n): fillp" fillp($n) 37 | a, b = fille(n), fillp(n) 38 | @bench "$(n): tsume" tsum($a) 39 | @bench "$(n): tsump" tsum($b) 40 | end # edges 41 | -------------------------------------------------------------------------------- /src/Parallel/dominatingset/minimal_dom_set.jl: -------------------------------------------------------------------------------- 1 | """ 2 | dominating_set(g, reps, MinimalDominatingSet(); parallel=:threads, seed=-1) 3 | 4 | Perform [`LightGraphs.dominating_set(g, MinimalDominatingSet())`](@ref) `reps` times in parallel 5 | and return the solution with the fewest vertices. 6 | 7 | ### Optional Arguements 8 | - `parallel=:threads`: If `parallel=:distributed` then the multiprocessor implementation is 9 | used. This implementation is more efficient if `reps` is large. 10 | - If `seed >= 0`, a random generator of each process/thread is seeded with this value. 11 | """ 12 | dominating_set(g::AbstractGraph{T}, reps::Integer, alg::MinimalDominatingSet; parallel=:threads, seed=-1) where T <: Integer = 13 | LightGraphs.Parallel.generate_reduce(g, (g::AbstractGraph{T})->LightGraphs.dominating_set(g, alg; seed=seed), 14 | (x::Vector{T}, y::Vector{T})->length(x) 1 5 | 6 | tests = [ 7 | "centrality/betweenness", 8 | "centrality/closeness", 9 | "centrality/pagerank", 10 | "centrality/radiality", 11 | "centrality/stress", 12 | "distance", 13 | "shortestpaths/bellman-ford", 14 | "shortestpaths/dijkstra", 15 | "shortestpaths/floyd-warshall", 16 | "shortestpaths/johnson", 17 | "traversals/bfs", 18 | "traversals/gdistances", 19 | "traversals/greedy_color", 20 | "dominatingset/minimal_dom_set", 21 | "independentset/maximal_ind_set", 22 | "vertexcover/random_vertex_cover", 23 | "utils" 24 | ] 25 | 26 | @testset "LightGraphs.Parallel" begin 27 | for t in tests 28 | tp = joinpath(testdir, "parallel", "$(t).jl") 29 | include(tp) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/parallel/centrality/radiality.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Radiality" begin 2 | gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") 3 | c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-rc.txt"), ',')) 4 | for g in testdigraphs(gint) 5 | zd = @inferred(Parallel.radiality_centrality(g; parallel=:distributed)) 6 | @test zd == c 7 | zt = @inferred(Parallel.radiality_centrality(g; parallel=:threads)) 8 | @test zt == c 9 | end 10 | 11 | g1 = cycle_graph(4) 12 | add_vertex!(g1) 13 | add_edge!(g1, 4, 5) 14 | for g in testgraphs(g1) 15 | zd = @inferred(Parallel.radiality_centrality(g; parallel=:distributed)) 16 | @test zd ≈ [5 // 6, 3 // 4, 5 // 6, 11 // 12, 2 // 3] 17 | zt = @inferred(Parallel.radiality_centrality(g; parallel=:threads)) 18 | @test zt ≈ [5 // 6, 3 // 4, 5 // 6, 11 // 12, 2 // 3] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/traversals/greedy_color.jl: -------------------------------------------------------------------------------- 1 | @testset "Greedy Coloring" begin 2 | 3 | g3 = star_graph(10) 4 | 5 | for g in testgraphs(g3) 6 | for op_sort in (true, false) 7 | C = @inferred(greedy_color(g, reps=5, sort_degree=op_sort)) 8 | @test C.num_colors == 2 9 | end 10 | end 11 | 12 | g4 = path_graph(20) 13 | g5 = complete_graph(20) 14 | 15 | for graph in [g4, g5] 16 | for g in testgraphs(graph) 17 | for op_sort in (true, false) 18 | C = @inferred(greedy_color(g, reps=5, sort_degree=op_sort)) 19 | 20 | @test C.num_colors <= maximum(degree(g))+1 21 | correct = true 22 | for e in edges(g) 23 | C.colors[src(e)] == C.colors[dst(e)] && (correct = false) 24 | end 25 | @test correct 26 | end 27 | end 28 | end 29 | end 30 | 31 | -------------------------------------------------------------------------------- /test/parallel/traversals/greedy_color.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Greedy Coloring" begin 2 | g3 = star_graph(10) 3 | for g in testgraphs(g3) 4 | for op_sort in (true, false) 5 | C = @inferred(Parallel.greedy_color(g, reps=5, sort_degree=op_sort)) 6 | @test C.num_colors == 2 7 | end 8 | end 9 | 10 | g4 = path_graph(20) 11 | g5 = complete_graph(20) 12 | 13 | for graph in [g4, g5] 14 | for g in testgraphs(graph) 15 | for op_sort in (true, false) 16 | C = @inferred(Parallel.greedy_color(g, reps=5, sort_degree=op_sort)) 17 | 18 | @test C.num_colors <= maximum(degree(g))+1 19 | correct = true 20 | for e in edges(g) 21 | C.colors[src(e)] == C.colors[dst(e)] && (correct = false) 22 | end 23 | @test correct 24 | end 25 | end 26 | end 27 | end 28 | 29 | -------------------------------------------------------------------------------- /benchmark/centrality.jl: -------------------------------------------------------------------------------- 1 | 2 | @benchgroup "centrality" begin 3 | @benchgroup "graphs" begin 4 | for (name, g) in GRAPHS 5 | @bench "$(name): degree" LightGraphs.degree_centrality($g) 6 | @bench "$(name): closeness" LightGraphs.closeness_centrality($g) 7 | if nv(g) < 1000 8 | @bench "$(name): betweenness" LightGraphs.betweenness_centrality($g) 9 | @bench "$(name): katz" LightGraphs.katz_centrality($g) 10 | end 11 | end 12 | end #graphs 13 | @benchgroup "digraphs" begin 14 | for (name, g) in DIGRAPHS 15 | @bench "$(name): degree" LightGraphs.degree_centrality($g) 16 | @bench "$(name): closeness" LightGraphs.closeness_centrality($g) 17 | if nv(g) < 1000 18 | @bench "$(name): betweenness" LightGraphs.betweenness_centrality($g) 19 | @bench "$(name): katz" LightGraphs.katz_centrality($g) 20 | end 21 | if nv(g) < 500 22 | @bench "$(name): pagerank" LightGraphs.pagerank($g) 23 | end 24 | end 25 | end # digraphs 26 | end # centrality 27 | -------------------------------------------------------------------------------- /src/community/core-periphery.jl: -------------------------------------------------------------------------------- 1 | """ 2 | core_periphery_deg(g) 3 | 4 | Compute the degree-based core-periphery for graph `g`. Return the vertex 5 | assignments (`1` for core and `2` for periphery) for each node in `g`. 6 | 7 | References: 8 | [Lip](http://arxiv.org/abs/1102.5511)) 9 | 10 | # Examples 11 | ```jldoctest 12 | julia> using LightGraphs 13 | 14 | julia> core_periphery_deg(star_graph(5)) 15 | 5-element Array{Int64,1}: 16 | 1 17 | 2 18 | 2 19 | 2 20 | 2 21 | 22 | julia> core_periphery_deg(path_graph(3)) 23 | 3-element Array{Int64,1}: 24 | 2 25 | 1 26 | 2 27 | ``` 28 | """ 29 | function core_periphery_deg end 30 | @traitfn function core_periphery_deg(g::::(!IsDirected)) 31 | degs = degree(g) 32 | p = sortperm(degs, rev=true) 33 | s = sum(degs) / 2. 34 | sbest = +Inf 35 | kbest = 0 36 | for k = 1:nv(g) - 1 37 | s = s + k - 1 - degree(g, p[k]) 38 | if s < sbest 39 | sbest = s 40 | kbest = k 41 | end 42 | end 43 | c = fill(2, nv(g)) 44 | c[p[1:kbest]] .= 1 45 | c 46 | end 47 | -------------------------------------------------------------------------------- /test/simplegraphs/simpleedge.jl: -------------------------------------------------------------------------------- 1 | @testset "SimpleEdge" begin 2 | e = SimpleEdge(1, 2) 3 | re = SimpleEdge(2, 1) 4 | 5 | @testset "edgetype: $(typeof(s))" for s in [0x01, UInt16(1), 1] 6 | T = typeof(s) 7 | d = s + one(T) 8 | p = Pair(s, d) 9 | 10 | ep1 = SimpleEdge(p) 11 | ep2 = SimpleEdge{UInt8}(p) 12 | ep3 = SimpleEdge{Int16}(p) 13 | 14 | t1 = (s, d) 15 | t2 = (s, d, "foo") 16 | 17 | @test src(ep1) == src(ep2) == src(ep3) == s 18 | @test dst(ep1) == dst(ep2) == dst(ep3) == s + one(T) 19 | 20 | @test eltype(ep1) == eltype(SimpleEdge{T}) == T 21 | 22 | @test eltype(p) == typeof(s) 23 | @test SimpleEdge(p) == e 24 | @test SimpleEdge(t1) == SimpleEdge(t2) == e 25 | @test SimpleEdge(t1) == SimpleEdge{UInt8}(t1) == SimpleEdge{Int16}(t1) 26 | @test SimpleEdge{Int64}(ep1) == e 27 | 28 | @test Pair(e) == p 29 | @test Tuple(e) == t1 30 | @test reverse(ep1) == re 31 | @test sprint(show, ep1) == "Edge 1 => 2" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /benchmark/core.jl: -------------------------------------------------------------------------------- 1 | function bench_iteredges(g::AbstractGraph) 2 | i = 0 3 | for e in edges(g) 4 | i += 1 5 | end 6 | return i 7 | end 8 | 9 | function bench_has_edge(g::AbstractGraph) 10 | seed!(1) 11 | nvg = nv(g) 12 | srcs = rand([1:nvg;], cld(nvg, 4)) 13 | dsts = rand([1:nvg;], cld(nvg, 4)) 14 | i = 0 15 | for (s, d) in zip(srcs, dsts) 16 | if has_edge(g, s, d) 17 | i += 1 18 | end 19 | end 20 | return i 21 | end 22 | 23 | 24 | EDGEFNS = [ 25 | bench_iteredges, 26 | bench_has_edge 27 | ] 28 | 29 | @benchgroup "edges" begin 30 | 31 | for fun in EDGEFNS 32 | @benchgroup "$fun" begin 33 | @benchgroup "graph" begin 34 | for (name, g) in GRAPHS 35 | @bench "$name" $fun($g) 36 | end 37 | end 38 | @benchgroup "digraph" begin 39 | for (name, g) in DIGRAPHS 40 | @bench "$name" $fun($g) 41 | end 42 | end # digraph 43 | end # fun 44 | end 45 | end # edges 46 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "LightGraphs" 2 | uuid = "093fc24a-ae57-5d10-9952-331d41423f4d" 3 | version = "1.3.0" 4 | 5 | [deps] 6 | ArnoldiMethod = "ec485272-7323-5ecc-a04f-4719b315124d" 7 | DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" 8 | Inflate = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" 9 | SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" 10 | ################################################## 11 | # Julia stdlib dependencies below 12 | ################################################## 13 | Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" 14 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 15 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 16 | SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383" 17 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 18 | Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 19 | 20 | [extras] 21 | Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" 22 | DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" 23 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 24 | 25 | [targets] 26 | test = ["Base64", "DelimitedFiles", "Test"] 27 | 28 | [compat] 29 | julia = "1" 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | sudo: false 3 | os: 4 | - linux 5 | # - osx 6 | 7 | julia: 8 | - 1.3 9 | - nightly 10 | 11 | allow_failures: 12 | - julia: nightly 13 | 14 | notifications: 15 | email: false 16 | 17 | jobs: 18 | include: 19 | - stage: "Documentation" 20 | julia: 1.3 21 | os: linux 22 | script: 23 | - julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' 24 | - julia --project=docs/ docs/make.jl 25 | name: "HTML" 26 | after_success: skip 27 | 28 | after_success: 29 | - julia -e 'using Pkg; import LightGraphs; cd(joinpath(dirname(pathof(LightGraphs)), "..")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' 30 | # - julia -e 'using Pkg; ps=Pkg.PackageSpec(name="Documenter", version="0.23.4"); Pkg.add(ps); Pkg.pin(ps)' 31 | # - julia -e 'using Pkg; import LightGraphs; joinpath(dirname(pathof(LightGraphs)); include(joinpath("docs", "make.jl"))' 32 | # - export JULIA_NUM_THREADS=4; julia -e 'Pkg.add("PkgBenchmark"); using PkgBenchmark; benchmarkpkg("LightGraphs", promptoverwrite=false)' 33 | -------------------------------------------------------------------------------- /src/Parallel/distance.jl: -------------------------------------------------------------------------------- 1 | # used in shortest path calculations 2 | 3 | function eccentricity(g::AbstractGraph, 4 | vs::AbstractVector=vertices(g), 5 | distmx::AbstractMatrix{T}=weights(g)) where T <: Real 6 | vlen = length(vs) 7 | eccs = SharedVector{T}(vlen) 8 | @sync @distributed for i = 1:vlen 9 | eccs[i] = maximum(LightGraphs.dijkstra_shortest_paths(g, vs[i], distmx).dists) 10 | end 11 | d = sdata(eccs) 12 | maximum(d) == typemax(T) && @warn("Infinite path length detected") 13 | return d 14 | end 15 | 16 | eccentricity(g::AbstractGraph, distmx::AbstractMatrix) = 17 | eccentricity(g, vertices(g), distmx) 18 | 19 | diameter(g::AbstractGraph, distmx::AbstractMatrix=weights(g)) = 20 | maximum(eccentricity(g, distmx)) 21 | 22 | periphery(g::AbstractGraph, distmx::AbstractMatrix=weights(g)) = 23 | LightGraphs.periphery(eccentricity(g, distmx)) 24 | 25 | radius(g::AbstractGraph, distmx::AbstractMatrix=weights(g)) = 26 | minimum(eccentricity(g, distmx)) 27 | 28 | center(g::AbstractGraph, distmx::AbstractMatrix=weights(g)) = 29 | LightGraphs.center(eccentricity(g, distmx)) 30 | -------------------------------------------------------------------------------- /benchmark/benchmarks.jl: -------------------------------------------------------------------------------- 1 | using PkgBenchmark 2 | using LightGraphs 3 | 4 | 5 | testdatadir = joinpath(dirname(@__FILE__), "..", "test", "testdata") 6 | benchdatadir = joinpath(dirname(@__FILE__), "data") 7 | paramsfile = joinpath(benchdatadir, "params.jld") 8 | 9 | println("testdatadir = $testdatadir") 10 | println("paramsfile = $paramsfile") 11 | 12 | dg1fn = joinpath(testdatadir, "graph-5k-50k.jgz") 13 | 14 | DIGRAPHS = Dict{String,DiGraph}( 15 | "complete100" => complete_digraph(100), 16 | "5000-50000" => LightGraphs.load(dg1fn)["graph-5000-50000"], 17 | "path500" => path_digraph(500) 18 | ) 19 | 20 | GRAPHS = Dict{String,Graph}( 21 | "complete100" => complete_graph(100), 22 | "tutte" => smallgraph(:tutte), 23 | "path500" => path_graph(500), 24 | "5000-49947" => SimpleGraph(DIGRAPHS["5000-50000"]) 25 | ) 26 | 27 | 28 | @benchgroup "LightGraphsBenchmarks" begin 29 | include("core.jl") 30 | include("parallel/egonets.jl") 31 | include("insertions.jl") 32 | include("edges.jl") 33 | include("centrality.jl") 34 | include("connectivity.jl") 35 | include("traversals.jl") 36 | end 37 | -------------------------------------------------------------------------------- /src/Parallel/Parallel.jl: -------------------------------------------------------------------------------- 1 | module Parallel 2 | 3 | using LightGraphs 4 | using LightGraphs: sample, AbstractPathState, JohnsonState, BellmanFordState, FloydWarshallState 5 | using Distributed: @distributed 6 | using Base.Threads: @threads, nthreads, Atomic, atomic_add!, atomic_cas! 7 | using SharedArrays: SharedMatrix, SharedVector, sdata 8 | using ArnoldiMethod 9 | using Random:shuffle 10 | import SparseArrays: sparse 11 | import Base: push!, popfirst!, isempty, getindex 12 | 13 | include("shortestpaths/bellman-ford.jl") 14 | include("shortestpaths/dijkstra.jl") 15 | include("shortestpaths/floyd-warshall.jl") 16 | include("shortestpaths/johnson.jl") 17 | include("centrality/betweenness.jl") 18 | include("centrality/closeness.jl") 19 | include("centrality/pagerank.jl") 20 | include("centrality/radiality.jl") 21 | include("centrality/stress.jl") 22 | include("distance.jl") 23 | include("traversals/bfs.jl") 24 | include("traversals/gdistances.jl") 25 | include("traversals/greedy_color.jl") 26 | include("utils.jl") 27 | include("dominatingset/minimal_dom_set.jl") 28 | include("independentset/maximal_ind_set.jl") 29 | include("vertexcover/random_vertex_cover.jl") 30 | 31 | end 32 | -------------------------------------------------------------------------------- /src/centrality/eigenvector.jl: -------------------------------------------------------------------------------- 1 | """ 2 | eigenvector_centrality(g) 3 | 4 | Compute the eigenvector centrality for the graph `g`. 5 | 6 | Eigenvector centrality computes the centrality for a node based on the 7 | centrality of its neighbors. The eigenvector centrality for node `i` is 8 | the \$i^{th}\$ element of \$\\mathbf{x}\$ in the equation 9 | `` 10 | \\mathbf{Ax} = λ \\mathbf{x} 11 | `` 12 | where \$\\mathbf{A}\$ is the adjacency matrix of the graph `g` 13 | with eigenvalue λ. 14 | 15 | By virtue of the Perron–Frobenius theorem, there is a unique and positive 16 | solution if λ is the largest eigenvalue associated with the 17 | eigenvector of the adjacency matrix \$\\mathbf{A}\$. 18 | 19 | ### References 20 | 21 | - Phillip Bonacich: Power and Centrality: A Family of Measures. 22 | American Journal of Sociology 92(5):1170–1182, 1986 23 | http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf 24 | - Mark E. J. Newman: Networks: An Introduction. 25 | Oxford University Press, USA, 2010, pp. 169. 26 | """ 27 | eigenvector_centrality(g::AbstractGraph) = abs.(vec(eigs(adjacency_matrix(g), which=LM(), nev=1)[2]))::Vector{Float64} 28 | -------------------------------------------------------------------------------- /docs/src/experimental.md: -------------------------------------------------------------------------------- 1 | # Experimental Graph Algorithms 2 | 3 | LightGraphs.Experimental is a module for graph algorithms that are newer or less stable. We can adopt algorithms before we finalize an interface for using them or if we feel that full support cannot be provided to the current implementation. You can expect new developments to land here before they make it into the main module. This enables the development to keep advancing without being risk averse because of stability guarantees. You can think of this module as a 0.X semantic version space; it is a place where you can play around with new algorithms, perspectives, and interfaces without fear of breaking critical code. 4 | 5 | ### A Note To Users 6 | Code in this module is unstable and subject to change. Do not use any code in this module in production environments without understanding the (large) risks involved. However, we welcome bug reports and issues via the normal channels.. 7 | 8 | ## Graph Isomorphism 9 | 10 | Here is the documentation for graph isomorphism functions. 11 | 12 | ```@autodocs 13 | Modules = [LightGraphs] 14 | Pages = [ 15 | "Experimental/isomorphism.jl", 16 | ] 17 | Private = false 18 | ``` 19 | -------------------------------------------------------------------------------- /test/testdata/graph-50-500-rc.txt: -------------------------------------------------------------------------------- 1 | 0.7142857142857143 2 | 0.7687074829931971 3 | 0.7619047619047619 4 | 0.6666666666666666 5 | 0.6462585034013606 6 | 0.6870748299319729 7 | 0.7074829931972788 8 | 0.7891156462585034 9 | 0.7142857142857143 10 | 0.7210884353741497 11 | 0.7074829931972788 12 | 0.5850340136054423 13 | 0.6462585034013606 14 | 0.7551020408163266 15 | 0.7210884353741497 16 | 0.7346938775510203 17 | 0.7823129251700681 18 | 0.673469387755102 19 | 0.7074829931972788 20 | 0.7210884353741497 21 | 0.6870748299319729 22 | 0.7346938775510203 23 | 0.7551020408163266 24 | 0.7210884353741497 25 | 0.6666666666666666 26 | 0.7482993197278912 27 | 0.6054421768707483 28 | 0.673469387755102 29 | 0.673469387755102 30 | 0.7006802721088435 31 | 0.6394557823129251 32 | 0.6938775510204082 33 | 0.6870748299319729 34 | 0.7006802721088435 35 | 0.7074829931972788 36 | 0.7482993197278912 37 | 0.7346938775510203 38 | 0.7142857142857143 39 | 0.6938775510204082 40 | 0.7074829931972788 41 | 0.7551020408163266 42 | 0.7414965986394558 43 | 0.6394557823129251 44 | 0.564625850340136 45 | 0.6258503401360543 46 | 0.7551020408163266 47 | 0.7006802721088435 48 | 0.7074829931972788 49 | 0.7551020408163266 50 | 0.6462585034013606 51 | -------------------------------------------------------------------------------- /test/shortestpaths/johnson.jl: -------------------------------------------------------------------------------- 1 | @testset "Johnson" begin 2 | g3 = path_graph(5) 3 | d = Symmetric([0 1 2 3 4; 1 0 6 7 8; 2 6 0 11 12; 3 7 11 0 16; 4 8 12 16 0]) 4 | for g in testgraphs(g3) 5 | z = @inferred(johnson_shortest_paths(g, d)) 6 | @test z.dists[3, :][:] == [7, 6, 0, 11, 27] 7 | @test z.parents[3, :][:] == [2, 3, 0, 3, 4] 8 | 9 | @test @inferred(enumerate_paths(z))[2][2] == [] 10 | @test @inferred(enumerate_paths(z))[2][4] == enumerate_paths(z, 2)[4] == enumerate_paths(z, 2, 4) == [2, 3, 4] 11 | end 12 | 13 | g4 = path_digraph(4) 14 | for g in testdigraphs(g4) 15 | z = @inferred(johnson_shortest_paths(g)) 16 | @test length(enumerate_paths(z, 4, 3)) == 0 17 | @test length(enumerate_paths(z, 4, 1)) == 0 18 | @test length(enumerate_paths(z, 2, 3)) == 2 19 | end 20 | 21 | g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) 22 | d = [0 3 8 0 -4; 0 0 0 1 7; 0 4 0 0 0; 2 0 -5 0 0; 0 0 0 6 0] 23 | for g in testdigraphs(g5) 24 | z = @inferred(johnson_shortest_paths(g, d)) 25 | @test z.dists == [0 1 -3 2 -4; 3 0 -4 1 -1; 7 4 0 5 3; 2 -1 -5 0 -2; 8 5 1 6 0] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/graphcut/karger_min_cut.jl: -------------------------------------------------------------------------------- 1 | @testset "Karger Minimum Cut" begin 2 | 3 | gx = path_graph(5) 4 | 5 | #Assumes cut[1] = 1 6 | for g in testgraphs(gx) 7 | cut = @inferred(karger_min_cut(g)) 8 | @test findfirst(isequal(2), cut) == findlast(isequal(1), cut)+1 9 | @test karger_cut_cost(g, cut) == 1 10 | @test karger_cut_edges(g, cut) == [Edge(findlast(isequal(1), cut), findfirst(isequal(2), cut))] 11 | end 12 | 13 | add_vertex!(gx) 14 | 15 | for g in testgraphs(gx) 16 | cut = @inferred(karger_min_cut(g)) 17 | @test cut == [1, 1, 1, 1, 1, 2] 18 | @test karger_cut_cost(g, cut) == 0 19 | @test karger_cut_edges(g, cut) == Vector{Edge}() 20 | 21 | end 22 | 23 | gx = star_graph(5) 24 | for g in testgraphs(gx) 25 | cut = @inferred(karger_min_cut(g)) 26 | @test count(isequal(2), cut) == 1 27 | @test karger_cut_cost(g, cut) == 1 28 | @test karger_cut_edges(g, cut) == [Edge(1, findfirst(isequal(2), cut))] 29 | end 30 | 31 | gx = SimpleGraph(1) 32 | for g in testgraphs(gx) 33 | cut = @inferred(karger_min_cut(g)) 34 | @test cut == zeros(Int, 1) 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - julia_version: 1.2 4 | - julia_version: nightly 5 | 6 | platform: 7 | - x86 # 32-bit 8 | - x64 # 64-bit 9 | 10 | # # Uncomment the following lines to allow failures on nightly julia 11 | # # (tests will run but not make your overall status red) 12 | # matrix: 13 | # allow_failures: 14 | # - julia_version: nightly 15 | 16 | branches: 17 | only: 18 | - master 19 | - /release-.*/ 20 | 21 | notifications: 22 | - provider: Email 23 | on_build_success: false 24 | on_build_failure: false 25 | on_build_status_changed: false 26 | 27 | install: 28 | - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) 29 | 30 | build_script: 31 | - echo "%JL_BUILD_SCRIPT%" 32 | - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" 33 | 34 | test_script: 35 | - echo "%JL_TEST_SCRIPT%" 36 | - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" 37 | 38 | # # Uncomment to support code coverage upload. Should only be enabled for packages 39 | # # which would have coverage gaps without running on Windows 40 | # on_success: 41 | # - echo "%JL_CODECOV_SCRIPT%" 42 | # - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" 43 | -------------------------------------------------------------------------------- /src/spanningtrees/prim.jl: -------------------------------------------------------------------------------- 1 | """ 2 | prim_mst(g, distmx=weights(g)) 3 | 4 | Return a vector of edges representing the minimum spanning tree of a connected, undirected graph `g` with optional 5 | distance matrix `distmx` using [Prim's algorithm](https://en.wikipedia.org/wiki/Prim%27s_algorithm). 6 | Return a vector of edges. 7 | """ 8 | function prim_mst end 9 | @traitfn function prim_mst(g::AG::(!IsDirected), 10 | distmx::AbstractMatrix{T}=weights(g)) where {T <: Real, U, AG <: AbstractGraph{U}} 11 | 12 | nvg = nv(g) 13 | 14 | pq = PriorityQueue{U, T}() 15 | finished = zeros(Bool, nvg) 16 | wt = fill(typemax(T), nvg) #Faster access time 17 | parents = zeros(U, nv(g)) 18 | 19 | pq[1] = typemin(T) 20 | wt[1] = typemin(T) 21 | 22 | while !isempty(pq) 23 | v = dequeue!(pq) 24 | finished[v] = true 25 | 26 | for u in neighbors(g, v) 27 | finished[u] && continue 28 | 29 | if wt[u] > distmx[u, v] 30 | wt[u] = distmx[u, v] 31 | pq[u] = wt[u] 32 | parents[u] = v 33 | end 34 | end 35 | end 36 | 37 | return [Edge{U}(parents[v], v) for v in vertices(g) if parents[v] != 0] 38 | end 39 | 40 | -------------------------------------------------------------------------------- /test/deprecations.jl: -------------------------------------------------------------------------------- 1 | @testset "Generator deprecations" begin 2 | types_0 = [BullGraph, ChvatalGraph, CubicalGraph, DesarguesGraph, 3 | DiamondGraph, DodecahedralGraph, FruchtGraph, HeawoodGraph, 4 | HouseGraph, HouseXGraph, IcosahedralGraph, KarateGraph, KrackhardtKiteGraph, 5 | MoebiusKantorGraph, OctahedralGraph, PappusGraph, PetersenGraph, 6 | SedgewickMazeGraph, TetrahedralGraph, TruncatedCubeGraph, 7 | TruncatedTetrahedronGraph, TruncatedTetrahedronDiGraph, TutteGraph] 8 | types_1param = [CompleteGraph, CompleteDiGraph, 9 | StarGraph, StarDigraph, PathGraph, 10 | PathDiGraph, CycleGraph, CycleDiGraph, WheelGraph, WheelDiGraph, 11 | BinaryTree, Doublebinary_tree, RoachGraph, 12 | LadderGraph, Circularladder_graph] 13 | types_2params = [CompleteBipartiteGraph, LollipopGraph, BarbellGraph, 14 | TuranGraph, CliqueGraph] 15 | for G in types_0 16 | @test_deprecated G() 17 | end 18 | for G in types_1param 19 | @test_deprecated G(5) 20 | end 21 | for G in types_2params 22 | @test_deprecated G(5, 2) 23 | end 24 | @test_deprecated CompleteMultipartiteGraph([3, 5]) 25 | @test_deprecated Grid([3, 5]) 26 | end 27 | -------------------------------------------------------------------------------- /test/parallel/shortestpaths/johnson.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Johnson" begin 2 | g3 = path_graph(5) 3 | d = Symmetric([0 1 2 3 4; 1 0 6 7 8; 2 6 0 11 12; 3 7 11 0 16; 4 8 12 16 0]) 4 | for g in testgraphs(g3) 5 | z = @inferred(Parallel.johnson_shortest_paths(g, d)) 6 | @test z.dists[3, :][:] == [7, 6, 0, 11, 27] 7 | @test z.parents[3, :][:] == [2, 3, 0, 3, 4] 8 | @test @inferred(enumerate_paths(z))[2][2] == [] 9 | @test @inferred(enumerate_paths(z))[2][4] == enumerate_paths(z, 2)[4] == enumerate_paths(z, 2, 4) == [2, 3, 4] 10 | end 11 | 12 | g4 = path_digraph(4) 13 | for g in testdigraphs(g4) 14 | z = @inferred(Parallel.johnson_shortest_paths(g)) 15 | @test length(enumerate_paths(z, 4, 3)) == 0 16 | @test length(enumerate_paths(z, 4, 1)) == 0 17 | @test length(enumerate_paths(z, 2, 3)) == 2 18 | end 19 | 20 | g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) 21 | d = [0 3 8 0 -4; 0 0 0 1 7; 0 4 0 0 0; 2 0 -5 0 0; 0 0 0 6 0] 22 | for g in testdigraphs(g5) 23 | z = @inferred(Parallel.johnson_shortest_paths(g, d)) 24 | @test z.dists == [0 1 -3 2 -4; 3 0 -4 1 -1; 7 4 0 5 3; 2 -1 -5 0 -2; 8 5 1 6 0] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/SimpleGraphs/simpleedge.jl: -------------------------------------------------------------------------------- 1 | import Base: Pair, Tuple, show, == 2 | import LightGraphs: AbstractEdge, src, dst, reverse 3 | 4 | abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T} end 5 | 6 | struct SimpleEdge{T<:Integer} <: AbstractSimpleEdge{T} 7 | src::T 8 | dst::T 9 | end 10 | 11 | SimpleEdge(t::Tuple) = SimpleEdge(t[1], t[2]) 12 | SimpleEdge(p::Pair) = SimpleEdge(p.first, p.second) 13 | SimpleEdge{T}(p::Pair) where T<:Integer = SimpleEdge(T(p.first), T(p.second)) 14 | SimpleEdge{T}(t::Tuple) where T<:Integer = SimpleEdge(T(t[1]), T(t[2])) 15 | 16 | eltype(::Type{<:ET}) where ET<:AbstractSimpleEdge{T} where T = T 17 | 18 | # Accessors 19 | src(e::AbstractSimpleEdge) = e.src 20 | dst(e::AbstractSimpleEdge) = e.dst 21 | 22 | # I/O 23 | show(io::IO, e::AbstractSimpleEdge) = print(io, "Edge $(e.src) => $(e.dst)") 24 | 25 | # Conversions 26 | Pair(e::AbstractSimpleEdge) = Pair(src(e), dst(e)) 27 | Tuple(e::AbstractSimpleEdge) = (src(e), dst(e)) 28 | 29 | SimpleEdge{T}(e::AbstractSimpleEdge) where T <: Integer = SimpleEdge{T}(T(e.src), T(e.dst)) 30 | 31 | # Convenience functions 32 | reverse(e::T) where T<:AbstractSimpleEdge = T(dst(e), src(e)) 33 | ==(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) = (src(e1) == src(e2) && dst(e1) == dst(e2)) 34 | -------------------------------------------------------------------------------- /test/testdata/graph-50-500-bc.txt: -------------------------------------------------------------------------------- 1 | 0.010249832446847709 2 | 0.02593845463948053 3 | 0.033331411335010705 4 | 0.008892710973391187 5 | 0.018714019529154732 6 | 0.02572085775407145 7 | 0.015492444979521855 8 | 0.029992085159905122 9 | 0.027007511895542183 10 | 0.016794971010294844 11 | 0.021656562254184462 12 | 0.007354463225568103 13 | 0.01110197010874716 14 | 0.013869527579435886 15 | 0.018546964827103097 16 | 0.029841068628644517 17 | 0.04349257525108208 18 | 0.01642897339509163 19 | 0.011499344137298933 20 | 0.021326159841515164 21 | 0.01289057098625209 22 | 0.011378939671892513 23 | 0.01624859832392153 24 | 0.020953269180330393 25 | 0.014688064410827267 26 | 0.01066689047603782 27 | 0.012286177396020675 28 | 0.012636268943403209 29 | 0.020236556257031826 30 | 0.017762651248290573 31 | 0.008697582688800885 32 | 0.020032663642622375 33 | 0.0106682810678792 34 | 0.009644645425215023 35 | 0.00935786383167922 36 | 0.02733622704926951 37 | 0.029367863975231022 38 | 0.02642544397413648 39 | 0.016314399681150983 40 | 0.021503954837572312 41 | 0.039519379690656636 42 | 0.02358530199348854 43 | 0.013167701502904 44 | 0.004943146444491197 45 | 0.010601646160938923 46 | 0.02673754316919046 47 | 0.018981979194139714 48 | 0.013817162311693746 49 | 0.03072045497388132 50 | 0.013124481566778232 51 | -------------------------------------------------------------------------------- /test/biconnectivity/articulation.jl: -------------------------------------------------------------------------------- 1 | @testset "Articulation" begin 2 | gint = SimpleGraph(13) 3 | add_edge!(gint, 1, 7) 4 | add_edge!(gint, 1, 2) 5 | add_edge!(gint, 1, 3) 6 | add_edge!(gint, 12, 13) 7 | add_edge!(gint, 10, 13) 8 | add_edge!(gint, 10, 12) 9 | add_edge!(gint, 12, 11) 10 | add_edge!(gint, 5, 4) 11 | add_edge!(gint, 6, 4) 12 | add_edge!(gint, 8, 9) 13 | add_edge!(gint, 6, 5) 14 | add_edge!(gint, 1, 6) 15 | add_edge!(gint, 7, 5) 16 | add_edge!(gint, 7, 3) 17 | add_edge!(gint, 7, 8) 18 | add_edge!(gint, 7, 10) 19 | add_edge!(gint, 7, 12) 20 | 21 | for g in testgraphs(gint) 22 | art = @inferred(articulation(g)) 23 | ans = [1, 7, 8, 12] 24 | @test art == ans 25 | end 26 | for level in 1:6 27 | btree = LightGraphs.binary_tree(level) 28 | for tree in [btree, Graph{UInt8}(btree), Graph{Int16}(btree)] 29 | artpts = @inferred(articulation(tree)) 30 | @test artpts == collect(1:(2^(level - 1) - 1)) 31 | end 32 | end 33 | 34 | hint = blockdiag(wheel_graph(5), wheel_graph(5)) 35 | add_edge!(hint, 5, 6) 36 | for h in (hint, Graph{UInt8}(hint), Graph{Int16}(hint)) 37 | @test @inferred(articulation(h)) == [5, 6] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/parallel/shortestpaths/floyd-warshall.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Floyd Warshall" begin 2 | g3 = path_graph(5) 3 | d = [0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0] 4 | for g in testgraphs(g3) 5 | z = @inferred(Parallel.floyd_warshall_shortest_paths(g, d)) 6 | @test z.dists[3, :][:] == [7, 6, 0, 11, 27] 7 | @test z.parents[3, :][:] == [2, 3, 0, 3, 4] 8 | 9 | @test @inferred(enumerate_paths(z))[2][2] == [] 10 | @test @inferred(enumerate_paths(z))[2][4] == enumerate_paths(z, 2)[4] == enumerate_paths(z, 2, 4) == [2, 3, 4] 11 | end 12 | g4 = path_digraph(4) 13 | d = ones(4, 4) 14 | for g in testdigraphs(g4) 15 | z = @inferred(Parallel.floyd_warshall_shortest_paths(g, d)) 16 | @test length(enumerate_paths(z, 4, 3)) == 0 17 | @test length(enumerate_paths(z, 4, 1)) == 0 18 | @test length(enumerate_paths(z, 2, 3)) == 2 19 | end 20 | 21 | g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) 22 | d = [0 3 8 0 -4; 0 0 0 1 7; 0 4 0 0 0; 2 0 -5 0 0; 0 0 0 6 0] 23 | for g in testdigraphs(g5) 24 | z = @inferred(Parallel.floyd_warshall_shortest_paths(g, d)) 25 | @test z.dists == [0 1 -3 2 -4; 3 0 -4 1 -1; 7 4 0 5 3; 2 -1 -5 0 -2; 8 5 1 6 0] 26 | end 27 | end -------------------------------------------------------------------------------- /test/spanningtrees/prim.jl: -------------------------------------------------------------------------------- 1 | @testset "Prim" begin 2 | g4 = complete_graph(4) 3 | 4 | distmx = [ 5 | 0 1 5 6 6 | 1 0 4 10 7 | 5 4 0 3 8 | 6 10 3 0 9 | ] 10 | 11 | vec_mst = Vector{Edge}([Edge(1, 2), Edge(2, 3), Edge(3, 4)]) 12 | for g in testgraphs(g4) 13 | # Testing Prim's algorithm 14 | mst = @inferred(prim_mst(g, distmx)) 15 | @test mst == vec_mst 16 | end 17 | 18 | #second test 19 | distmx_sec = [ 20 | 0 0 0.26 0 0.38 0 0.58 0.16 21 | 0 0 0.36 0.29 0 0.32 0 0.19 22 | 0.26 0.36 0 0.17 0 0 0.4 0.34 23 | 0 0.29 0.17 0 0 0 0.52 0 24 | 0.38 0 0 0 0 0.35 0.93 0.37 25 | 0 0.32 0 0 0.35 0 0 0.28 26 | 0.58 0 0.4 0.52 0.93 0 0 0 27 | 0.16 0.19 0.34 0 0.37 0.28 0 0 28 | ] 29 | 30 | vec2 = Vector{Edge}([Edge(8, 2), Edge(1, 3), Edge(3, 4), Edge(6, 5), Edge(8, 6), Edge(3, 7), Edge(1, 8)]) 31 | gx = SimpleGraph(distmx_sec) 32 | for g in testgraphs(gx) 33 | mst2 = @inferred(prim_mst(g, distmx_sec)) 34 | @test mst2 == vec2 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/Parallel/shortestpaths/johnson.jl: -------------------------------------------------------------------------------- 1 | function johnson_shortest_paths(g::AbstractGraph{U}, 2 | distmx::AbstractMatrix{T}=weights(g)) where T <: Real where U <: Integer 3 | 4 | nvg = nv(g) 5 | type_distmx = typeof(distmx) 6 | #Change when parallel implementation of Bellman Ford available 7 | wt_transform = bellman_ford_shortest_paths(g, vertices(g), distmx).dists 8 | 9 | if !type_distmx.mutable && type_distmx != LightGraphs.DefaultDistance 10 | distmx = sparse(distmx) #Change reference, not value 11 | end 12 | 13 | #Weight transform not needed if all weights are positive. 14 | if type_distmx != LightGraphs.DefaultDistance 15 | for e in edges(g) 16 | distmx[src(e), dst(e)] += wt_transform[src(e)] - wt_transform[dst(e)] 17 | end 18 | end 19 | 20 | 21 | dijk_state = Parallel.dijkstra_shortest_paths(g, vertices(g), distmx) 22 | dists = dijk_state.dists 23 | parents = dijk_state.parents 24 | 25 | 26 | broadcast!(-, dists, dists, wt_transform) 27 | for v in vertices(g) 28 | dists[:, v] .+= wt_transform[v] #Vertical traversal prefered 29 | end 30 | 31 | if type_distmx.mutable 32 | for e in edges(g) 33 | distmx[src(e), dst(e)] += wt_transform[dst(e)] - wt_transform[src(e)] 34 | end 35 | end 36 | 37 | return JohnsonState(dists, parents) 38 | end 39 | 40 | -------------------------------------------------------------------------------- /test/simplegraphs/generators/binomial.jl: -------------------------------------------------------------------------------- 1 | # NOTE: These tests are not part of the active test suite, because they require Distributions.jl. 2 | # DO NOT INCORPORATE INTO runtests.jl. 3 | 4 | using Distributions 5 | using LightGraphs 6 | using StatsBase 7 | using Test 8 | import Random 9 | 10 | import Base: - 11 | import LightGraphs: randbn 12 | import StatsBase: SummaryStats 13 | 14 | function -(s::SummaryStats, t::SummaryStats) 15 | return SummaryStats(s.mean - t.mean, 16 | s.min - t.min, 17 | s.q25 - t.q25, 18 | s.median - t.median, 19 | s.q75 - t.q75, 20 | s.max - t.max) 21 | end 22 | function binomial_test(n, p, s) 23 | drand = rand(Binomial(n, p), s) 24 | lrand = Int64[randbn(n, p) for i in 1:s] 25 | 26 | ds = summarystats(drand) 27 | ls = summarystats(lrand) 28 | dσ = std(drand) 29 | lσ = std(lrand) 30 | 31 | summarydiff = ds - ls 32 | @test abs(summarydiff.mean) / ds.mean < .10 33 | @test abs(summarydiff.median) / ds.median < .10 34 | @test abs(summarydiff.q25) / ds.q25 < .10 35 | @test abs(summarydiff.q75) / ds.q75 < .10 36 | 37 | @test abs(dσ - lσ) / dσ < .10 38 | end 39 | seed!(1234) 40 | n = 10000 41 | p = 0.3 42 | s = 100000 43 | 44 | @testset "($n, $p, $s)" for (n, p, s) in [(100, 0.3, 1000), (1000, 0.8, 1000), (10000, 0.25, 1000)] 45 | binomial_test(n, p, s) 46 | end 47 | 48 | -------------------------------------------------------------------------------- /src/spanningtrees/kruskal.jl: -------------------------------------------------------------------------------- 1 | """ 2 | kruskal_mst(g, distmx=weights(g); minimize=true) 3 | 4 | Return a vector of edges representing the minimum (by default) spanning tree of a connected, 5 | undirected graph `g` with optional distance matrix `distmx` using [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm). 6 | 7 | ### Optional Arguments 8 | - `minimize=true`: if set to `false`, calculate the maximum spanning tree. 9 | """ 10 | function kruskal_mst end 11 | # see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax 12 | @traitfn function kruskal_mst(g::AG::(!IsDirected), 13 | distmx::AbstractMatrix{T}=weights(g); minimize=true) where {T <: Real, U, AG <: AbstractGraph{U}} 14 | 15 | connected_vs = IntDisjointSets(nv(g)) 16 | 17 | mst = Vector{edgetype(g)}() 18 | sizehint!(mst, nv(g) - 1) 19 | 20 | weights = Vector{T}() 21 | sizehint!(weights, ne(g)) 22 | edge_list = collect(edges(g)) 23 | for e in edge_list 24 | push!(weights, distmx[src(e), dst(e)]) 25 | end 26 | 27 | for e in edge_list[sortperm(weights; rev=!minimize)] 28 | if !in_same_set(connected_vs, src(e), dst(e)) 29 | union!(connected_vs, src(e), dst(e)) 30 | push!(mst, e) 31 | (length(mst) >= nv(g) - 1) && break 32 | end 33 | end 34 | 35 | return mst 36 | end 37 | 38 | -------------------------------------------------------------------------------- /test/parallel/centrality/stress.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Stress" begin 2 | gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") 3 | c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-sc.txt"), ',')) 4 | for g in testdigraphs(gint) 5 | 6 | z = LightGraphs.stress_centrality(g) 7 | zd = @inferred(Parallel.stress_centrality(g; parallel=:distributed)) 8 | @test z == zd == c 9 | zt = @inferred(Parallel.stress_centrality(g; parallel=:threads)) 10 | @test z == zt == c 11 | 12 | xd = Parallel.stress_centrality(g, 3; parallel=:distributed) 13 | @test length(xd) == 50 14 | xt = Parallel.stress_centrality(g, 3; parallel=:threasd) 15 | @test length(xt) == 50 16 | 17 | xd2 = Parallel.stress_centrality(g, collect(1:20); parallel=:distributed) 18 | @test length(xd2) == 50 19 | xt2 = Parallel.stress_centrality(g, collect(1:20); parallel=:threads) 20 | @test length(xt2) == 50 21 | end 22 | 23 | g1 = cycle_graph(4) 24 | add_vertex!(g1) 25 | add_edge!(g1, 4, 5) 26 | 27 | for g in testgraphs(g1) 28 | zd = Parallel.stress_centrality(g; parallel=:distributed) 29 | @test zd == [4, 2, 4, 10, 0] 30 | zt = Parallel.stress_centrality(g; parallel=:threads) 31 | @test zt == [4, 2, 4, 10, 0] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/independentset/maximal_ind_set.jl: -------------------------------------------------------------------------------- 1 | export MaximalIndependentSet 2 | 3 | struct MaximalIndependentSet end 4 | 5 | """ 6 | independent_set(g, MaximalIndependentSet(); seed=-1) 7 | 8 | Find a random set of vertices that are independent (no two vertices are adjacent to each other) and 9 | it is not possible to insert a vertex into the set without sacrificing the independence property. 10 | 11 | ### Implementation Notes 12 | Performs [Approximate Maximum Independent Set](https://en.wikipedia.org/wiki/Maximal_independent_set#Sequential_algorithm) once. 13 | Returns a vector of vertices representing the vertices in the independent set. 14 | 15 | ### Performance 16 | Runtime: O(|V|+|E|) 17 | Memory: O(|V|) 18 | Approximation Factor: maximum(degree(g))+1 19 | 20 | ### Optional Arguments 21 | - If `seed >= 0`, a random generator is seeded with this value. 22 | """ 23 | function independent_set( 24 | g::AbstractGraph{T}, 25 | alg::MaximalIndependentSet; 26 | seed::Int=-1 27 | ) where T <: Integer 28 | 29 | nvg = nv(g) 30 | ind_set = Vector{T}() 31 | sizehint!(ind_set, nvg) 32 | deleted = falses(nvg) 33 | 34 | for v in randperm(getRNG(seed), nvg) 35 | (deleted[v] || has_edge(g, v, v)) && continue 36 | 37 | deleted[v] = true 38 | push!(ind_set, v) 39 | deleted[neighbors(g, v)] .= true 40 | end 41 | 42 | return ind_set 43 | end 44 | -------------------------------------------------------------------------------- /src/Parallel/centrality/radiality.jl: -------------------------------------------------------------------------------- 1 | radiality_centrality(g::AbstractGraph; parallel=:distributed) = 2 | parallel == :distributed ? distr_radiality_centrality(g) : threaded_radiality_centrality(g) 3 | 4 | function distr_radiality_centrality(g::AbstractGraph)::Vector{Float64} 5 | n_v = nv(g) 6 | vs = vertices(g) 7 | n = ne(g) 8 | meandists = SharedVector{Float64}(Int(n_v)) 9 | maxdists = SharedVector{Float64}(Int(n_v)) 10 | 11 | @sync @distributed for i = 1:n_v 12 | d = LightGraphs.dijkstra_shortest_paths(g, vs[i]) 13 | maxdists[i] = maximum(d.dists) 14 | meandists[i] = sum(d.dists) / (n_v - 1) 15 | nothing 16 | end 17 | dmtr = maximum(maxdists) 18 | radialities = collect(meandists) 19 | return ((dmtr + 1) .- radialities) ./ dmtr 20 | end 21 | 22 | function threaded_radiality_centrality(g::AbstractGraph)::Vector{Float64} 23 | n_v = nv(g) 24 | vs = vertices(g) 25 | n = ne(g) 26 | meandists = Vector{Float64}(undef, n_v) 27 | maxdists = Vector{Float64}(undef, n_v) 28 | 29 | Base.Threads.@threads for i in vertices(g) 30 | d = LightGraphs.dijkstra_shortest_paths(g, vs[i]) 31 | maxdists[i] = maximum(d.dists) 32 | meandists[i] = sum(d.dists) / (n_v - 1) 33 | end 34 | dmtr = maximum(maxdists) 35 | radialities = collect(meandists) 36 | return ((dmtr + 1) .- radialities) ./ dmtr 37 | end 38 | -------------------------------------------------------------------------------- /src/vertexcover/random_vertex_cover.jl: -------------------------------------------------------------------------------- 1 | export RandomVertexCover 2 | 3 | struct RandomVertexCover end 4 | 5 | """ 6 | vertex_cover(g, RandomVertexCover(); seed=-1) 7 | 8 | Find a set of vertices such that every edge in `g` has some vertex in the set as 9 | atleast one of its end point. 10 | 11 | ### Implementation Notes 12 | Performs [Approximate Minimum Vertex Cover](https://en.wikipedia.org/wiki/Vertex_cover#Approximate_evaluation) once. 13 | Returns a vector of vertices representing the vertices in the Vertex Cover. 14 | 15 | ### Performance 16 | Runtime: O(|V|+|E|) 17 | Memory: O(|E|) 18 | Approximation Factor: 2 19 | 20 | ### Optional Arguments 21 | - If `seed >= 0`, a random generator is seeded with this value. 22 | """ 23 | function vertex_cover( 24 | g::AbstractGraph{T}, 25 | alg::RandomVertexCover; 26 | seed::Int=-1 27 | ) where T <: Integer 28 | 29 | (ne(g) > 0) || return Vector{T}() #Shuffle raises error 30 | nvg = nv(g) 31 | in_cover = falses(nvg) 32 | length_cover = 0 33 | 34 | @inbounds for e in shuffle(getRNG(seed), collect(edges(g))) 35 | u = src(e) 36 | v = dst(e) 37 | if !(in_cover[u] || in_cover[v]) 38 | in_cover[u] = in_cover[v] = true 39 | length_cover += (v != u ? 2 : 1) 40 | end 41 | end 42 | 43 | return LightGraphs.findall!(in_cover, Vector{T}(undef, length_cover)) 44 | end 45 | 46 | -------------------------------------------------------------------------------- /test/dominatingset/degree_dom_set.jl: -------------------------------------------------------------------------------- 1 | @testset "Degree Dominating Set" begin 2 | 3 | g0 = SimpleGraph(0) 4 | for g in testgraphs(g0) 5 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 6 | @test isempty(d) 7 | end 8 | 9 | g1 = SimpleGraph(1) 10 | for g in testgraphs(g1) 11 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 12 | @test (d == [1,]) 13 | end 14 | 15 | add_edge!(g1, 1, 1) 16 | for g in testgraphs(g1) 17 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 18 | @test (d == [1,]) 19 | end 20 | 21 | g3 = star_graph(5) 22 | for g in testgraphs(g1) 23 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 24 | @test (d == [1,]) 25 | end 26 | 27 | g4 = complete_graph(5) 28 | for g in testgraphs(g4) 29 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 30 | @test length(d)== 1 31 | end 32 | 33 | #path_graph(5) with additional edge 2-5 34 | g5 = path_graph(5) 35 | add_edge!(g5, 2, 5) 36 | for g in testgraphs(g5) 37 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 38 | @test (length(d)== 2 && minimum(d) == 2) 39 | end 40 | 41 | add_edge!(g5, 2, 2) 42 | add_edge!(g5, 3, 3) 43 | for g in testgraphs(g5) 44 | d = @inferred(dominating_set(g, DegreeDominatingSet())) 45 | @test (length(d)== 2 && minimum(d) == 2) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/independentset/degree_ind_set.jl: -------------------------------------------------------------------------------- 1 | @testset "Degree Independent Set" begin 2 | 3 | g0 = SimpleGraph(0) 4 | for g in testgraphs(g0) 5 | c = @inferred(independent_set(g, DegreeIndependentSet())) 6 | @test isempty(c) 7 | end 8 | 9 | g1 = SimpleGraph(1) 10 | for g in testgraphs(g1) 11 | c = @inferred(independent_set(g, DegreeIndependentSet())) 12 | @test (c == [1,]) 13 | end 14 | 15 | add_edge!(g1, 1, 1) 16 | for g in testgraphs(g1) 17 | c = @inferred(independent_set(g, DegreeIndependentSet())) 18 | @test isempty(c) 19 | end 20 | 21 | g3 = star_graph(5) 22 | for g in testgraphs(g3) 23 | c = @inferred(independent_set(g, DegreeIndependentSet())) 24 | @test sort(c) == [2, 3, 4, 5] 25 | end 26 | 27 | g4 = complete_graph(5) 28 | for g in testgraphs(g4) 29 | c = @inferred(independent_set(g, DegreeIndependentSet())) 30 | @test length(c)== 1 #Exactly one vertex 31 | end 32 | 33 | #path_graph(5) with additional edge 2-5 34 | g5 = path_graph(5) 35 | add_edge!(g5, 2, 5) 36 | for g in testgraphs(g5) 37 | c = @inferred(independent_set(g, DegreeIndependentSet())) 38 | @test sort(c) == [1, 3, 5] 39 | end 40 | 41 | add_edge!(g5, 2, 2) 42 | add_edge!(g5, 3, 3) 43 | for g in testgraphs(g5) 44 | c = @inferred(independent_set(g, DegreeIndependentSet())) 45 | @test sort(c) == [1, 5] 46 | end 47 | end -------------------------------------------------------------------------------- /src/Parallel/shortestpaths/dijkstra.jl: -------------------------------------------------------------------------------- 1 | """ 2 | struct Parallel.MultipleDijkstraState{T, U} 3 | 4 | An [`AbstractPathState`](@ref) designed for Parallel.dijkstra_shortest_paths calculation. 5 | """ 6 | struct MultipleDijkstraState{T <: Real,U <: Integer} <: AbstractPathState 7 | dists::Matrix{T} 8 | parents::Matrix{U} 9 | end 10 | 11 | """ 12 | Parallel.dijkstra_shortest_paths(g, sources=vertices(g), distmx=weights(g)) 13 | 14 | Compute the shortest paths between all pairs of vertices in graph `g` by running 15 | [`dijkstra_shortest_paths`] for every vertex and using an optional list of source vertex `sources` and 16 | an optional distance matrix `distmx`. Return a [`Parallel.MultipleDijkstraState`](@ref) with relevant 17 | traversal information. 18 | """ 19 | function dijkstra_shortest_paths(g::AbstractGraph{U}, 20 | sources::AbstractVector=vertices(g), 21 | distmx::AbstractMatrix{T}=weights(g)) where T <: Real where U 22 | 23 | n_v = nv(g) 24 | r_v = length(sources) 25 | 26 | # TODO: remove `Int` once julialang/#23029 / #23032 are resolved 27 | dists = SharedMatrix{T}(Int(r_v), Int(n_v)) 28 | parents = SharedMatrix{U}(Int(r_v), Int(n_v)) 29 | 30 | @sync @distributed for i in 1:r_v 31 | state = LightGraphs.dijkstra_shortest_paths(g, sources[i], distmx) 32 | dists[i, :] = state.dists 33 | parents[i, :] = state.parents 34 | end 35 | 36 | result = MultipleDijkstraState(sdata(dists), sdata(parents)) 37 | return result 38 | end 39 | -------------------------------------------------------------------------------- /src/centrality/radiality.jl: -------------------------------------------------------------------------------- 1 | """ 2 | radiality_centrality(g) 3 | 4 | Calculate the [radiality centrality](http://www.cbmc.it/fastcent/doc/Radiality.htm) 5 | of a graph `g` across all vertices. Return a vector representing the centrality 6 | calculated for each node in `g`. 7 | 8 | The radiality centrality ``R_u`` of a vertex ``u`` is defined as 9 | `` 10 | R_u = \\frac{D_g + 1 - \\frac{\\sum_{v∈V}d_{u,v}}{|V|-1}}{D_g} 11 | `` 12 | 13 | where ``D_g`` is the diameter of the graph and ``d_{u,v}`` is the 14 | length of the shortest path from ``u`` to ``v``. 15 | 16 | ### References 17 | - Brandes, U.: A faster algorithm for betweenness centrality. J Math Sociol 25 (2001) 163-177 18 | 19 | # Examples 20 | ```jldoctest 21 | julia> using LightGraphs 22 | 23 | julia> radiality_centrality(star_graph(4)) 24 | 4-element Array{Float64,1}: 25 | 1.0 26 | 0.6666666666666666 27 | 0.6666666666666666 28 | 0.6666666666666666 29 | 30 | julia> radiality_centrality(path_graph(3)) 31 | 3-element Array{Float64,1}: 32 | 0.75 33 | 1.0 34 | 0.75 35 | ``` 36 | """ 37 | function radiality_centrality(g::AbstractGraph)::Vector{Float64} 38 | n_v = nv(g) 39 | vs = vertices(g) 40 | 41 | meandists = zeros(Float64, n_v) 42 | dmtr = 0.0 43 | for v in vs 44 | d = dijkstra_shortest_paths(g, v) 45 | dmtr = max(dmtr, maximum(d.dists)) 46 | meandists[v] = sum(d.dists) / (n_v - 1) # ignore the source vx 47 | end 48 | meandists = (dmtr + 1) .- (meandists) 49 | return meandists ./ dmtr 50 | end 51 | -------------------------------------------------------------------------------- /test/biconnectivity/bridge.jl: -------------------------------------------------------------------------------- 1 | @testset "Bridge" begin 2 | gint = SimpleGraph(13) 3 | add_edge!(gint, 1, 7) 4 | add_edge!(gint, 1, 2) 5 | add_edge!(gint, 1, 3) 6 | add_edge!(gint, 12, 13) 7 | add_edge!(gint, 10, 13) 8 | add_edge!(gint, 10, 12) 9 | add_edge!(gint, 12, 11) 10 | add_edge!(gint, 5, 4) 11 | add_edge!(gint, 6, 4) 12 | add_edge!(gint, 8, 9) 13 | add_edge!(gint, 6, 5) 14 | add_edge!(gint, 1, 6) 15 | add_edge!(gint, 7, 5) 16 | add_edge!(gint, 7, 3) 17 | add_edge!(gint, 7, 8) 18 | add_edge!(gint, 7, 10) 19 | add_edge!(gint, 7, 12) 20 | 21 | for g in testgraphs(gint) 22 | brd = @inferred(bridges(g)) 23 | ans = [ 24 | Edge(1, 2), 25 | Edge(8, 9), 26 | Edge(7, 8), 27 | Edge(11, 12), 28 | ] 29 | @test brd == ans 30 | end 31 | for level in 1:6 32 | btree = LightGraphs.binary_tree(level) 33 | for tree in [btree, Graph{UInt8}(btree), Graph{Int16}(btree)] 34 | brd = @inferred(bridges(tree)) 35 | ans = collect(edges(tree)) 36 | @test Set(brd) == Set(ans) 37 | end 38 | end 39 | 40 | hint = blockdiag(wheel_graph(5), wheel_graph(5)) 41 | add_edge!(hint, 5, 6) 42 | for h in (hint, Graph{UInt8}(hint), Graph{Int16}(hint)) 43 | @test @inferred(bridges(h)) == [ 44 | Edge(5, 6), 45 | ] 46 | end 47 | 48 | dir = SimpleDiGraph(10, 10) 49 | @test_throws MethodError bridges(dir) 50 | end 51 | -------------------------------------------------------------------------------- /test/traversals/maxadjvisit.jl: -------------------------------------------------------------------------------- 1 | 2 | @testset "Max adj visit" begin 3 | gx = SimpleGraph(8) 4 | 5 | # Test of Min-Cut and maximum adjacency visit 6 | # Original example by Stoer 7 | 8 | wedges = [ 9 | (1, 2, 2.), 10 | (1, 5, 3.), 11 | (2, 3, 3.), 12 | (2, 5, 2.), 13 | (2, 6, 2.), 14 | (3, 4, 4.), 15 | (3, 7, 2.), 16 | (4, 7, 2.), 17 | (4, 8, 2.), 18 | (5, 6, 3.), 19 | (6, 7, 1.), 20 | (7, 8, 3.)] 21 | 22 | 23 | m = length(wedges) 24 | eweights = spzeros(nv(gx), nv(gx)) 25 | 26 | for (s, d, w) in wedges 27 | add_edge!(gx, s, d) 28 | eweights[s, d] = w 29 | eweights[d, s] = w 30 | end 31 | for g in testgraphs(gx) 32 | @test nv(g) == 8 33 | @test ne(g) == m 34 | 35 | parity, bestcut = @inferred(mincut(g, eweights)) 36 | 37 | @test length(parity) == 8 38 | @test parity == [2, 2, 1, 1, 2, 2, 1, 1] 39 | @test bestcut == 4.0 40 | 41 | parity, bestcut = @inferred(mincut(g)) 42 | 43 | @test length(parity) == 8 44 | @test parity == [2, 1, 1, 1, 1, 1, 1, 1] 45 | @test bestcut == 2.0 46 | 47 | v = @inferred(maximum_adjacency_visit(g)) 48 | @test v == Vector{Int64}([1, 2, 5, 6, 3, 7, 4, 8]) 49 | end 50 | 51 | g1 = SimpleGraph(1) 52 | for g in testgraphs(g1) 53 | @test @inferred(maximum_adjacency_visit(g)) == collect(vertices(g)) 54 | @test @inferred(mincut(g)) == ([1], zero(eltype(g))) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /docs/src/graphtypes.md: -------------------------------------------------------------------------------- 1 | # Graph Types 2 | 3 | In addition to providing `SimpleGraph` and `SimpleDiGraph` implementations, LightGraphs also serves as a framework for other graph types. Currently, there are several alternative graph types, each with its own package: 4 | 5 | - [SimpleWeightedGraphs](https://github.com/JuliaGraphs/SimpleWeightedGraphs.jl) provides a structure for (un)directed graphs with the ability to specify weights on edges. 6 | - [MetaGraphs](https://github.com/JuliaGraphs/MetaGraphs.jl) provides a structure (un)directed graphs that supports user-defined properties on the graph, vertices, and edges. 7 | - [StaticGraphs](https://github.com/JuliaGraphs/StaticGraphs.jl) supports very large graph structures in a space- and time-efficient manner, but as the name implies, does not allow modification of the graph once created. 8 | 9 | ### Which Graph Type Should I Use? 10 | 11 | These are general guidelines to help you select the proper graph type. 12 | 13 | - In general, prefer the native `SimpleGraphs`/`SimpleDiGraphs` structures in [LightGraphs.jl](https://github.com/JuliaGraphs/LightGraphs.jl). 14 | - If you need edge weights and don't require large numbers of graph modifications, use [SimpleWeightedGraphs](https://github.com/JuliaGraphs/SimpleWeightedGraphs.jl). 15 | - If you need labeling of vertices or edges, use [MetaGraphs](https://github.com/JuliaGraphs/MetaGraphs.jl). 16 | - If you work with very large graphs (billons to tens of billions of edges) and don't need mutability, use [StaticGraphs](https://github.com/JuliaGraphs/StaticGraphs.jl). 17 | -------------------------------------------------------------------------------- /src/vertexcover/degree_vertex_cover.jl: -------------------------------------------------------------------------------- 1 | export DegreeVertexCover 2 | 3 | struct DegreeVertexCover end 4 | 5 | """ 6 | vertex_cover(g, DegreeVertexCover()) 7 | 8 | Obtain a vertex cover using a greedy heuristic. 9 | 10 | ### Implementation Notes 11 | An edge is said to be covered if it has at least one end-point in the vertex cover. 12 | Initialise the vertex cover to an empty set and iteratively choose the vertex with the most uncovered 13 | edges. 14 | 15 | ### Performance 16 | Runtime: O((|V|+|E|)*log(|V|)) 17 | Memory: O(|V|) 18 | 19 | # Examples 20 | ```jldoctest 21 | julia> using LightGraphs 22 | 23 | julia> vertex_cover(path_graph(3), DegreeVertexCover()) 24 | 1-element Array{Int64,1}: 25 | 2 26 | 27 | julia> vertex_cover(cycle_graph(3), DegreeVertexCover()) 28 | 2-element Array{Int64,1}: 29 | 1 30 | 3 31 | ``` 32 | """ 33 | function vertex_cover( 34 | g::AbstractGraph{T}, 35 | alg::DegreeVertexCover 36 | ) where T <: Integer 37 | 38 | nvg = nv(g) 39 | in_cover = falses(nvg) 40 | length_cover = 0 41 | degree_queue = PriorityQueue(Base.Order.Reverse, enumerate(degree(g))) 42 | 43 | while !isempty(degree_queue) && peek(degree_queue)[2] > 0 44 | v = dequeue!(degree_queue) 45 | in_cover[v] = true 46 | length_cover += 1 47 | 48 | @inbounds @simd for u in neighbors(g, v) 49 | if !in_cover[u] 50 | degree_queue[u] -= 1 51 | end 52 | end 53 | end 54 | return LightGraphs.findall!(in_cover, Vector{T}(undef, length_cover)) 55 | end 56 | 57 | -------------------------------------------------------------------------------- /test/simplegraphs/generators/euclideangraphs.jl: -------------------------------------------------------------------------------- 1 | @testset "Euclidean graphs" begin 2 | N = 10 3 | d = 2 4 | g, weights, points = @inferred(euclidean_graph(N, d)) 5 | @test nv(g) == N 6 | @test ne(g) == N * (N - 1) ÷ 2 7 | @test (d, N) == size(points) 8 | @test maximum(x -> x[2], weights) <= sqrt(d) 9 | @test minimum(x -> x[2], weights) >= 0 10 | @test maximum(points) <= 1 11 | @test minimum(points) >= 0. 12 | 13 | g, weights, points = @inferred(euclidean_graph(N, d, bc=:periodic)) 14 | @test maximum(x -> x[2], weights) <= sqrt(d / 2) 15 | @test minimum(x -> x[2], weights) >= 0. 16 | @test maximum(points) <= 1 17 | @test minimum(points) >= 0. 18 | 19 | 20 | @test_throws DomainError euclidean_graph(points, L=0.01, bc=:periodic) 21 | @test_throws ArgumentError euclidean_graph(points, bc=:badbc) 22 | 23 | # In our algorithm we ensure, that the resulting graph has the same 24 | # number of vertices as we have points. For this we have a special case, 25 | # where we have to insert vertices if the vertices with the highest indices are 26 | # isolated. This test ensures that this case is covered. 27 | @testset "Euclidean graph with vertex of highest index isolated" begin 28 | point1 = [1.0, 0.0, 0.0] 29 | point2 = [0.0, 1.0, 0.0] 30 | point3 = [0.0, 0.0, 100.0] 31 | matrix = hcat(point1, point2, point3) 32 | g, _ = euclidean_graph(matrix, cutoff= 5.0) 33 | @test has_edge(g, 1, 2) && ne(g) == 1 34 | @test nv(g) == 3 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/traversals/bipartition.jl: -------------------------------------------------------------------------------- 1 | @testset "Bipartiteness" begin 2 | g6 = smallgraph(:house) 3 | for g in testgraphs(g6) 4 | @test @inferred(!is_bipartite(g)) 5 | end 6 | 7 | gx = SimpleGraph(5) 8 | add_edge!(gx, 1, 2); add_edge!(gx, 1, 4) 9 | add_edge!(gx, 2, 3); add_edge!(gx, 2, 5) 10 | add_edge!(gx, 3, 4) 11 | 12 | for g in testgraphs(gx) 13 | @test @inferred(is_bipartite(g)) 14 | end 15 | 16 | g10 = complete_graph(10) 17 | for g in testgraphs(g10) 18 | @test @inferred(bipartite_map(g)) == Vector{eltype(g)}() 19 | end 20 | 21 | g10 = complete_bipartite_graph(10, 10) 22 | for g in testgraphs(g10) 23 | T = eltype(g) 24 | @test @inferred(bipartite_map(g)) == Vector{T}([ones(T, 10); 2 * ones(T, 10)]) 25 | 26 | h = blockdiag(g, g) 27 | @test @inferred(bipartite_map(h)) == Vector{T}([ones(T, 10); 2 * ones(T, 10); ones(T, 10); 2 * ones(T, 10)]) 28 | end 29 | 30 | g2 = complete_graph(2) 31 | for g in testgraphs(g2) 32 | @test @inferred(bipartite_map(g)) == Vector{eltype(g)}([1, 2]) 33 | end 34 | 35 | g2 = Graph(2) 36 | for g in testgraphs(g2) 37 | @test @inferred(bipartite_map(g)) == Vector{eltype(g)}([1, 1]) 38 | end 39 | 40 | g2 = DiGraph(2) 41 | for g in testdigraphs(g2) 42 | @test @inferred(bipartite_map(g)) == Vector{eltype(g)}([1, 1]) 43 | end 44 | 45 | g2 = path_digraph(2) 46 | for g in testdigraphs(g2) 47 | @test @inferred(bipartite_map(g)) == Vector{eltype(g)}([1, 2]) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /docs/src/persistence.md: -------------------------------------------------------------------------------- 1 | # Reading and writing Graphs 2 | 3 | ```@index 4 | Pages = ["persistence.md"] 5 | ``` 6 | 7 | ## Saving using *LightGraphs.jl* `lg` format. 8 | 9 | Graphs may be written to I/O streams and files using the `savegraph` function and read with the `loadgraph` function. The default graph format is a bespoke compressed *LightGraphs.jl* format `LG`. 10 | 11 | ### Example 12 | 13 | ```julia 14 | 15 | g = erdos_renyi(5, 0.2) 16 | 17 | savegraph("mygraph.lgz", g) 18 | reloaded_g = loadgraph("mygraph.lgz") 19 | ``` 20 | 21 | In addition, graphs can also be saved in an uncompressed format using the `compress=false` option. 22 | 23 | ```julia 24 | 25 | savegraph("mygraph.lg", g, compress=false) 26 | 27 | reloaded_g = loadgraph("mygraph.lg") 28 | ``` 29 | 30 | Finally, dictionaries of graphs can also be saved and subsequently re-loaded one by one. 31 | 32 | ```julia 33 | graph_dict = {"g1" => erdos_renyi(5, 0.1), 34 | "g2" => erdos_renyi(10, 0.2), 35 | "g3" => erdos_renyi(2, 0.9)} 36 | 37 | savegraph("mygraph_dict.lg", graph_dict) 38 | 39 | # Re-load only graph g1 40 | reloaded_g1 = loadgraph("mygraph_dict.lg", "g1") 41 | ``` 42 | 43 | ## Full docs 44 | 45 | ```@autodocs 46 | Modules = [LightGraphs] 47 | Pages = ["persistence/common.jl"] 48 | Private = false 49 | ``` 50 | 51 | ## Reading and Writing using other formats using GraphIO 52 | 53 | The [GraphIO.jl](https://github.com/JuliaGraphs/GraphIO.jl) library provides tools for importing and exporting graph objects using common file types like edgelists, GraphML, Pajek NET, and more. 54 | -------------------------------------------------------------------------------- /test/vertexcover/random_vertex_cover.jl: -------------------------------------------------------------------------------- 1 | @testset "Random Vertex Cover" begin 2 | 3 | g0 = SimpleGraph(0) 4 | for g in testgraphs(g0) 5 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 6 | @test isempty(c) 7 | end 8 | 9 | g1 = SimpleGraph(1) 10 | for g in testgraphs(g1) 11 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 12 | @test isempty(c) 13 | end 14 | 15 | add_edge!(g1, 1, 1) 16 | for g in testgraphs(g1) 17 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 18 | @test c == [1,] 19 | end 20 | 21 | g3 = star_graph(5) 22 | for g in testgraphs(g3) 23 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 24 | @test (length(c)== 2 && (c[1] == 1 || c[2] == 1)) 25 | end 26 | 27 | g4 = complete_graph(5) 28 | for g in testgraphs(g4) 29 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 30 | @test length(c)== 4 #All except one vertex 31 | end 32 | 33 | g5 = path_graph(5) 34 | for g in testgraphs(g5) 35 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 36 | sort!(c) 37 | @test (c == [1, 2, 3, 4] || c == [1, 2, 4, 5] || c == [2, 3, 4, 5]) 38 | end 39 | 40 | add_edge!(g5, 2, 2) 41 | add_edge!(g5, 3, 3) 42 | for g in testgraphs(g5) 43 | c = @inferred(vertex_cover(g, RandomVertexCover(); seed=3)) 44 | sort!(c) 45 | @test (c == [1, 2, 3, 4] || c == [1, 2, 3, 4, 5] || c == [2, 3, 4] || c == [2, 3, 4, 5]) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/independentset/maximal_ind_set.jl: -------------------------------------------------------------------------------- 1 | @testset "Maximal Independent Set" begin 2 | 3 | g0 = SimpleGraph(0) 4 | for g in testgraphs(g0) 5 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 6 | @test isempty(z) 7 | end 8 | 9 | g1 = SimpleGraph(1) 10 | for g in testgraphs(g1) 11 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 12 | @test (z == [1,]) 13 | end 14 | 15 | add_edge!(g1, 1, 1) 16 | for g in testgraphs(g1) 17 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 18 | isempty(z) 19 | end 20 | 21 | g3 = star_graph(5) 22 | for g in testgraphs(g3) 23 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 24 | @test (length(z)== 1 || length(z)== 4) 25 | end 26 | 27 | g4 = complete_graph(5) 28 | for g in testgraphs(g4) 29 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 30 | @test length(z)== 1 #Exactly one vertex 31 | end 32 | 33 | g5 = path_graph(5) 34 | for g in testgraphs(g5) 35 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 36 | sort!(z) 37 | @test (z == [2, 4] || z == [2, 5] || z == [1, 3, 5] || z == [1, 4]) 38 | end 39 | 40 | add_edge!(g5, 2, 2) 41 | add_edge!(g5, 3, 3) 42 | for g in testgraphs(g5) 43 | z = @inferred(independent_set(g, MaximalIndependentSet(); seed=3)) 44 | sort!(z) 45 | @test (z == [4,] || z == [5,] || z == [1, 5] || z == [1, 4]) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/parallel/distance.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Distance" begin 2 | g4 = path_digraph(5) 3 | adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph 4 | adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph 5 | a1 = SimpleGraph(adjmx1) 6 | a2 = SimpleDiGraph(adjmx2) 7 | distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] 8 | distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] 9 | 10 | for g in testgraphs(a1) 11 | z = @inferred(LightGraphs.eccentricity(g, distmx1)) 12 | y = @inferred(Parallel.eccentricity(g, distmx1)) 13 | @test isapprox(y, z) 14 | @test @inferred(LightGraphs.diameter(y)) == @inferred(Parallel.diameter(g, distmx1)) == 6.2 15 | @test @inferred(LightGraphs.periphery(y)) == @inferred(Parallel.periphery(g, distmx1)) == [1, 3] 16 | @test @inferred(LightGraphs.radius(y)) == @inferred(Parallel.radius(g, distmx1)) == 4.2 17 | @test @inferred(LightGraphs.center(y)) == @inferred(Parallel.center(g, distmx1)) == [2] 18 | end 19 | 20 | for g in testdigraphs(a2) 21 | z = @inferred(LightGraphs.eccentricity(g, distmx2)) 22 | y = @inferred(Parallel.eccentricity(g, distmx2)) 23 | @test isapprox(y, z) 24 | @test @inferred(LightGraphs.diameter(y)) == @inferred(Parallel.diameter(g, distmx2)) == 6.2 25 | @test @inferred(LightGraphs.periphery(y)) == @inferred(Parallel.periphery(g, distmx2)) == [1] 26 | @test @inferred(LightGraphs.radius(y)) == @inferred(Parallel.radius(g, distmx2)) == 4.2 27 | @test @inferred(LightGraphs.center(y)) == @inferred(Parallel.center(g, distmx2)) == [2] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /docs/src/developing.md: -------------------------------------------------------------------------------- 1 | # Developing Alternate Graph Types 2 | 3 | This section is designed to guide developers who wish to write their own graph structures. 4 | 5 | All LightGraphs functions rely on a standard API to function. As long as your graph structure is a subtype of 6 | [`AbstractGraph`](@ref) and implements the following API functions with the given return values, all functions 7 | within the LightGraphs package should just work: 8 | 9 | - [`edges`](@ref) 10 | - [Base.eltype](https://docs.julialang.org/en/latest/base/collections/#Base.eltype) 11 | - [`edgetype`](@ref) (example: `edgetype(g::CustomGraph) = LightGraphs.SimpleEdge{eltype(g)})`) 12 | - [`has_edge`](@ref) 13 | - [`has_vertex`](@ref) 14 | - [`inneighbors`](@ref) 15 | - [`ne`](@ref) 16 | - [`nv`](@ref) 17 | - [`outneighbors`](@ref) 18 | - [`vertices`](@ref) 19 | - [`is_directed`](@ref): Note that since LightGraphs uses traits to determine directedness, `is_directed` for a `CustomGraph` type 20 | should be implemented with **both** of the following signatures: 21 | - `is_directed(::Type{CustomGraph})::Bool` (example: `is_directed(::Type{<:CustomGraph}) = false`) 22 | - `is_directed(g::CustomGraph)::Bool` 23 | - [`zero`](@ref) 24 | 25 | If the graph structure is designed to represent weights on edges, the [`weights`](@ref) function should also be defined. 26 | Note that the output does not necessarily have to be a dense matrix, but it must be a subtype of `AbstractMatrix{<:Real}` and indexable via `[u, v]`. 27 | 28 | #### Note on inheriting from AbstractSimpleGraph 29 | 30 | Every subtype of AbstractSimpleGraph must return neighbors in ascending order. 31 | -------------------------------------------------------------------------------- /test/centrality/pagerank.jl: -------------------------------------------------------------------------------- 1 | @testset "Pagerank" begin 2 | function dense_pagerank_solver(g::AbstractGraph, α=0.85::Real) 3 | p = fill(1 / nv(g), nv(g)) 4 | danglingnodes = outdegree(g) .== 0 5 | M = Matrix{Float64}(adjacency_matrix(g)) 6 | M = M' 7 | M[:, danglingnodes] .= sum(danglingnodes) ./ nv(g) 8 | M = M * Diagonal(1 ./ sum(M, dims=1)[:]) 9 | @assert all(1.01 .>= sum(M, dims=1) .>= 0.999) 10 | # v = inv(I-β*M) * ((1-β)/nv(g) * ones(nv(g), 1)) 11 | v = inv(I - α * M) * ((1 - α) / nv(g) * ones(nv(g), 1)) 12 | return v 13 | end 14 | 15 | g5 = SimpleDiGraph(4) 16 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 17 | g6 = SimpleGraph(4) 18 | add_edge!(g6, 1, 2); add_edge!(g6, 2, 3); add_edge!(g6, 1, 3); add_edge!(g6, 3, 4) 19 | for α in [0.75, 0.85] 20 | 21 | for g in testdigraphs(g5) 22 | @test pagerank(g)[3] ≈ 0.318 atol = 0.001 23 | @test length(@inferred(pagerank(g))) == nv(g) 24 | @test_throws ErrorException pagerank(g, 2) 25 | @test_throws ErrorException pagerank(g, α, 2) 26 | @test isapprox(pagerank(g, α), dense_pagerank_solver(g, α), atol=0.001) 27 | end 28 | 29 | for g in testgraphs(g6) 30 | @test length(@inferred(pagerank(g))) == nv(g) 31 | @test_throws ErrorException pagerank(g, 2) 32 | @test_throws ErrorException pagerank(g, α, 2) 33 | @test isapprox(pagerank(g, α), dense_pagerank_solver(g, α), atol=0.001) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /docs/src/errorhandling.md: -------------------------------------------------------------------------------- 1 | ## Error Handling 2 | 3 | In an ideal world, all software would work perfectly all the time. However, in 4 | the real world software encounters errors due to the outside world, bad input, bugs, or 5 | programmer error. 6 | 7 | ### Types of Errors 8 | 9 | #### Sentinel Values 10 | It is the position of this project that error conditions that happen often, 11 | typically due to bad data should be handled with sentinel values returned to 12 | indicate the failure condition. These are used for functions such as 13 | `add_edge!(g, u, v)`. If you try to add an edge with negative vertex numbers, or 14 | vertices that exceed the number of vertices in the graph, then you will get a 15 | return value of `false`. 16 | 17 | #### Errors / Exceptions 18 | 19 | For more severe failures such as bad arguments or failure to converge, we use 20 | exceptions. The primary distinction between Sentinel Values and Argument Errors 21 | has to do with the run time of the function being called. In a function that is 22 | expected to be a called in a tight loop such as `add_edge!`, we will use a 23 | sentinel value rather than an exception. This is because it is faster to do a 24 | simple if statement to handle the error than a full try/catch block. For 25 | functions that take longer to run, we use Exceptions. If you find an exception 26 | with an error message that isn't helpful for debugging, please file a bug 27 | report so that we can improve these messages. 28 | 29 | - ArgumentError: the inputs to this function are not valid 30 | - InexactError: there are types that cannot express something with the necessary 31 | precision 32 | -------------------------------------------------------------------------------- /src/independentset/degree_ind_set.jl: -------------------------------------------------------------------------------- 1 | export DegreeIndependentSet 2 | 3 | struct DegreeIndependentSet end 4 | 5 | """ 6 | independent_set(g, DegreeIndependentSet()) 7 | 8 | Obtain an [independent set](https://en.wikipedia.org/wiki/Independent_set_(graph_theory)) of 9 | `g` using a greedy heuristic based on the degree of the vertices. 10 | 11 | ### Implementation Notes 12 | A vertex is said to be valid if it is not in the independent set or adjacent to any vertex 13 | in the independent set. 14 | Initilalise the independent set to an empty set and iteratively choose the vertex that is 15 | adjacent to the fewest valid vertices in the independent set until all vertices are invalid. 16 | 17 | ### Performance 18 | Runtime: O((|V|+|E|)*log(|V|)) 19 | Memory: O(|V|) 20 | """ 21 | function independent_set( 22 | g::AbstractGraph{T}, 23 | alg::DegreeIndependentSet 24 | ) where T <: Integer 25 | 26 | nvg = nv(g) 27 | ind_set = Vector{T}() 28 | sizehint!(ind_set, nvg) 29 | deleted = falses(nvg) 30 | degree_queue = PriorityQueue(enumerate(degree(g))) 31 | 32 | while !isempty(degree_queue) 33 | v = dequeue!(degree_queue) 34 | (deleted[v] || has_edge(g, v, v)) && continue 35 | deleted[v] = true 36 | push!(ind_set, v) 37 | 38 | for u in neighbors(g, v) 39 | deleted[u] && continue 40 | deleted[u] = true 41 | @inbounds @simd for w in neighbors(g, u) 42 | if !deleted[w] 43 | degree_queue[w] -= 1 44 | end 45 | end 46 | end 47 | end 48 | return ind_set 49 | end 50 | -------------------------------------------------------------------------------- /test/dominatingset/minimal_dom_set.jl: -------------------------------------------------------------------------------- 1 | @testset "Minimal Dominating Set" begin 2 | 3 | g0 = SimpleGraph(0) 4 | for g in testgraphs(g0) 5 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 6 | @test isempty(d) 7 | end 8 | 9 | g1 = SimpleGraph(1) 10 | for g in testgraphs(g1) 11 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 12 | @test (d == [1,]) 13 | end 14 | 15 | add_edge!(g1, 1, 1) 16 | for g in testgraphs(g1) 17 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 18 | @test (d == [1,]) 19 | end 20 | 21 | g3 = star_graph(5) 22 | for g in testgraphs(g3) 23 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 24 | @test (length(d)== 1 || (length(d)== 4 && minimum(d) > 1 )) 25 | end 26 | 27 | g4 = complete_graph(5) 28 | for g in testgraphs(g4) 29 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 30 | @test length(d)== 1 #Exactly one vertex 31 | end 32 | 33 | g5 = path_graph(4) 34 | for g in testgraphs(g5) 35 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 36 | sort!(d) 37 | @test (d == [1, 2, 4] || d == [1, 3] || d == [1, 4] || d == [2, 3] || d == [2, 4]) 38 | end 39 | 40 | add_edge!(g5, 2, 2) 41 | add_edge!(g5, 3, 3) 42 | for g in testgraphs(g5) 43 | d = @inferred(dominating_set(g, MinimalDominatingSet(); seed=3)) 44 | sort!(d) 45 | @test (d == [1, 2, 4] || d == [1, 3] || d == [1, 4] || d == [2, 3] || d == [2, 4]) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/biconnectivity/biconnect.jl: -------------------------------------------------------------------------------- 1 | @testset "Biconnect" begin 2 | gint = SimpleGraph(12) 3 | add_edge!(gint, 1, 2) 4 | add_edge!(gint, 2, 3) 5 | add_edge!(gint, 2, 4) 6 | add_edge!(gint, 3, 4) 7 | add_edge!(gint, 3, 5) 8 | add_edge!(gint, 4, 5) 9 | add_edge!(gint, 2, 6) 10 | add_edge!(gint, 1, 7) 11 | add_edge!(gint, 6, 7) 12 | add_edge!(gint, 6, 8) 13 | add_edge!(gint, 6, 9) 14 | add_edge!(gint, 8, 9) 15 | add_edge!(gint, 9, 10) 16 | add_edge!(gint, 11, 12) 17 | 18 | a = [[Edge(3, 5), Edge(4, 5), Edge(2, 4), Edge(3, 4), Edge(2, 3)], 19 | [Edge(9, 10)], 20 | [Edge(6, 9), Edge(8, 9), Edge(6, 8)], 21 | [Edge(1, 7), Edge(6, 7), Edge(2, 6), Edge(1, 2)], 22 | [Edge(11, 12)]] 23 | 24 | for g in testgraphs(gint) 25 | bcc = @inferred(biconnected_components(g)) 26 | @test bcc == a 27 | @test typeof(bcc) === Vector{Vector{Edge{eltype(g)}}} 28 | end 29 | 30 | g = SimpleGraph(4) 31 | add_edge!(g, 1, 2) 32 | add_edge!(g, 2, 3) 33 | add_edge!(g, 3, 4) 34 | add_edge!(g, 1, 4) 35 | 36 | h = SimpleGraph(4) 37 | add_edge!(h, 1, 2) 38 | add_edge!(h, 2, 3) 39 | add_edge!(h, 3, 4) 40 | add_edge!(h, 1, 4) 41 | 42 | gint = blockdiag(g, h) 43 | add_edge!(gint, 4, 5) 44 | 45 | a = [[Edge(5, 8), Edge(7, 8), Edge(6, 7), Edge(5, 6)], [Edge(4, 5)], [Edge(1, 4), Edge(3, 4), Edge(2, 3), Edge(1, 2)]] 46 | 47 | for g in testgraphs(gint) 48 | bcc = @inferred(biconnected_components(g)) 49 | @test bcc == a 50 | @test typeof(bcc) === Vector{Vector{Edge{eltype(g)}}} 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/centrality/closeness.jl: -------------------------------------------------------------------------------- 1 | @testset "Closeness" begin 2 | g5 = SimpleDiGraph(4) 3 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 4 | 5 | for g in testdigraphs(g5) 6 | y = @inferred(closeness_centrality(g; normalize=false)) 7 | z = @inferred(closeness_centrality(g)) 8 | @test y == [0.75, 0.6666666666666666, 1.0, 0.0] 9 | @test z == [0.75, 0.4444444444444444, 0.3333333333333333, 0.0] 10 | end 11 | 12 | adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph 13 | a2 = SimpleDiGraph(adjmx2) 14 | for g in testdigraphs(a2) 15 | distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] 16 | c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] 17 | y = @inferred(closeness_centrality(g, distmx2; normalize=false)) 18 | z = @inferred(closeness_centrality(g, distmx2)) 19 | @test isapprox(y, c2) 20 | @test isapprox(z, c2) 21 | end 22 | 23 | g5 = SimpleGraph(5) 24 | add_edge!(g5, 1, 2) 25 | for g in testgraphs(g5) 26 | z = @inferred(closeness_centrality(g)) 27 | @test z[1] == z[2] == 0.25 28 | @test z[3] == z[4] == z[5] == 0.0 29 | end 30 | 31 | adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph 32 | a1 = SimpleGraph(adjmx1) 33 | for g in testgraphs(a1) 34 | distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] 35 | c1 = [0.24390243902439027, 0.3225806451612903, 0.1923076923076923] 36 | y = @inferred(closeness_centrality(g, distmx1; normalize=false)) 37 | z = @inferred(closeness_centrality(g, distmx1)) 38 | @test isapprox(y, c1) 39 | @test isapprox(z, c1) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /src/centrality/degree.jl: -------------------------------------------------------------------------------- 1 | function _degree_centrality(g::AbstractGraph, gtype::Integer; normalize=true) 2 | n_v = nv(g) 3 | c = zeros(n_v) 4 | for v in vertices(g) 5 | if gtype == 0 # count both in and out degree if appropriate 6 | deg = is_directed(g) ? outdegree(g, v) + indegree(g, v) : outdegree(g, v) 7 | elseif gtype == 1 # count only in degree 8 | deg = indegree(g, v) 9 | else # count only out degree 10 | deg = outdegree(g, v) 11 | end 12 | s = normalize ? (1.0 / (n_v - 1.0)) : 1.0 13 | c[v] = deg * s 14 | end 15 | return c 16 | end 17 | 18 | """ 19 | degree_centrality(g) 20 | indegree_centrality(g) 21 | outdegree_centrality(g) 22 | 23 | Calculate the [degree centrality](https://en.wikipedia.org/wiki/Centrality#Degree_centrality) 24 | of graph `g`. Return a vector representing the centrality calculated for each node in `g`. 25 | 26 | ### Optional Arguments 27 | - `normalize=true`: If true, normalize each centrality measure by ``\\frac{1}{|V|-1}``. 28 | 29 | # Examples 30 | ```jldoctest 31 | julia> using LightGraphs 32 | 33 | julia> degree_centrality(star_graph(4)) 34 | 4-element Array{Float64,1}: 35 | 1.0 36 | 0.3333333333333333 37 | 0.3333333333333333 38 | 0.3333333333333333 39 | 40 | julia> degree_centrality(path_graph(3)) 41 | 3-element Array{Float64,1}: 42 | 0.5 43 | 1.0 44 | 0.5 45 | ``` 46 | """ 47 | degree_centrality(g::AbstractGraph; all...) = _degree_centrality(g, 0; all...) 48 | indegree_centrality(g::AbstractGraph; all...) = _degree_centrality(g, 1; all...) 49 | outdegree_centrality(g::AbstractGraph; all...) = _degree_centrality(g, 2; all...) 50 | -------------------------------------------------------------------------------- /test/community/cliques.jl: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | # 3 | # Maximal cliques of undirected graph 4 | # Derived from Graphs.jl: https://github.com/julialang/Graphs.jl 5 | # 6 | ################################################################## 7 | 8 | @testset "Cliques" begin 9 | function setofsets(array_of_arrays) 10 | Set(map(Set, array_of_arrays)) 11 | end 12 | 13 | function test_cliques(graph, expected) 14 | # Make test results insensitive to ordering 15 | setofsets(@inferred(maximal_cliques(graph))) == setofsets(expected) 16 | end 17 | 18 | gx = SimpleGraph(3) 19 | add_edge!(gx, 1, 2) 20 | for g in testgraphs(gx) 21 | @test test_cliques(g, Array[[1, 2], [3]]) 22 | end 23 | add_edge!(gx, 2, 3) 24 | for g in testgraphs(gx) 25 | @test test_cliques(g, Array[[1, 2], [2, 3]]) 26 | end 27 | # Test for "pivotdonenbrs not defined" bug 28 | h = SimpleGraph(6) 29 | add_edge!(h, 1, 2) 30 | add_edge!(h, 1, 3) 31 | add_edge!(h, 1, 4) 32 | add_edge!(h, 2, 5) 33 | add_edge!(h, 2, 6) 34 | add_edge!(h, 3, 4) 35 | add_edge!(h, 3, 6) 36 | add_edge!(h, 5, 6) 37 | 38 | for g in testgraphs(h) 39 | @test !isempty(@inferred(maximal_cliques(g))) 40 | end 41 | 42 | # test for extra cliques bug 43 | 44 | h = SimpleGraph(7) 45 | add_edge!(h, 1, 3) 46 | add_edge!(h, 2, 6) 47 | add_edge!(h, 3, 5) 48 | add_edge!(h, 3, 6) 49 | add_edge!(h, 4, 5) 50 | add_edge!(h, 4, 7) 51 | add_edge!(h, 5, 7) 52 | for g in testgraphs(h) 53 | @test test_cliques(h, Array[[7, 4, 5], [2, 6], [3, 5], [3, 6], [3, 1]]) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/centrality/closeness.jl: -------------------------------------------------------------------------------- 1 | """ 2 | closeness_centrality(g, distmx=weights(g); normalize=true) 3 | 4 | Calculate the [closeness centrality](https://en.wikipedia.org/wiki/Centrality#Closeness_centrality) 5 | of the graph `g`. Return a vector representing the centrality calculated for each node in `g`. 6 | 7 | ### Optional Arguments 8 | - `normalize=true`: If true, normalize the centrality value of each 9 | node `n` by ``\\frac{|δ_n|}{|V|-1}``, where ``δ_n`` is the set of vertices reachable 10 | from node `n`. 11 | 12 | # Examples 13 | ```jldoctest 14 | julia> using LightGraphs 15 | 16 | julia> closeness_centrality(star_graph(5)) 17 | 5-element Array{Float64,1}: 18 | 1.0 19 | 0.5714285714285714 20 | 0.5714285714285714 21 | 0.5714285714285714 22 | 0.5714285714285714 23 | 24 | julia> closeness_centrality(path_graph(4)) 25 | 4-element Array{Float64,1}: 26 | 0.5 27 | 0.75 28 | 0.75 29 | 0.5 30 | ``` 31 | """ 32 | function closeness_centrality(g::AbstractGraph, 33 | distmx::AbstractMatrix=weights(g); 34 | normalize=true) 35 | 36 | n_v = nv(g) 37 | closeness = zeros(n_v) 38 | 39 | for u in vertices(g) 40 | if degree(g, u) == 0 # no need to do Dijkstra here 41 | closeness[u] = 0.0 42 | else 43 | d = dijkstra_shortest_paths(g, u, distmx).dists 44 | δ = filter(x -> x != typemax(x), d) 45 | σ = sum(δ) 46 | l = length(δ) - 1 47 | if σ > 0 48 | closeness[u] = l / σ 49 | if normalize 50 | n = l / (n_v - 1) 51 | closeness[u] *= n 52 | end 53 | end 54 | end 55 | end 56 | return closeness 57 | end 58 | -------------------------------------------------------------------------------- /test/spanningtrees/kruskal.jl: -------------------------------------------------------------------------------- 1 | @testset "Kruskal" begin 2 | g4 = complete_graph(4) 3 | 4 | distmx = [ 5 | 0 1 5 6 6 | 1 0 4 10 7 | 5 4 0 3 8 | 6 10 3 0 9 | ] 10 | 11 | vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) 12 | max_vec_mst = Vector{Edge}([Edge(2, 4), Edge(1, 4), Edge(1, 3)]) 13 | for g in testgraphs(g4) 14 | # Testing Kruskal's algorithm 15 | mst = @inferred(kruskal_mst(g, distmx)) 16 | @test mst == vec_mst 17 | @test @inferred(kruskal_mst(g, distmx, minimize=false)) == max_vec_mst 18 | end 19 | #second test 20 | distmx_sec = [ 21 | 0 0 0.26 0 0.38 0 0.58 0.16 22 | 0 0 0.36 0.29 0 0.32 0 0.19 23 | 0.26 0.36 0 0.17 0 0 0.4 0.34 24 | 0 0.29 0.17 0 0 0 0.52 0 25 | 0.38 0 0 0 0 0.35 0.93 0.37 26 | 0 0.32 0 0 0.35 0 0 0.28 27 | 0.58 0 0.4 0.52 0.93 0 0 0 28 | 0.16 0.19 0.34 0 0.37 0.28 0 0 29 | ] 30 | 31 | gx = SimpleGraph(distmx_sec) 32 | vec2 = Vector{Edge}([Edge(1, 8), Edge(3, 4), Edge(2, 8), Edge(1, 3), Edge(6, 8), Edge(5, 6), Edge(3, 7)]) 33 | max_vec2 = Vector{Edge}([Edge(5, 7), Edge(1, 7), Edge(4, 7), Edge(3, 7), Edge(5, 8), Edge(2, 3), Edge(5, 6)]) 34 | for g in testgraphs(gx) 35 | mst2 = @inferred(kruskal_mst(g, distmx_sec)) 36 | @test mst2 == vec2 37 | @test @inferred(kruskal_mst(g, distmx_sec, minimize=false)) == max_vec2 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/parallel/centrality/pagerank.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Pagerank" begin 2 | function dense_pagerank_solver(g::AbstractGraph, α=0.85::Real) 3 | p = fill(1 / nv(g), nv(g)) 4 | danglingnodes = outdegree(g) .== 0 5 | M = Matrix{Float64}(adjacency_matrix(g)) 6 | M = M' 7 | M[:, danglingnodes] .= sum(danglingnodes) ./ nv(g) 8 | M = M * Diagonal(1 ./ sum(M, dims=1)[:]) 9 | @assert all(1.01 .>= sum(M, dims=1) .>= 0.999) 10 | # v = inv(I-β*M) * ((1-β)/nv(g) * ones(nv(g), 1)) 11 | v = inv(I - α * M) * ((1 - α) / nv(g) * ones(nv(g), 1)) 12 | return v 13 | end 14 | 15 | g5 = SimpleDiGraph(4) 16 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 17 | g6 = SimpleGraph(4) 18 | add_edge!(g6, 1, 2); add_edge!(g6, 2, 3); add_edge!(g6, 1, 3); add_edge!(g6, 3, 4) 19 | for α in [0.75, 0.85] 20 | 21 | for g in testdigraphs(g5) 22 | @test Parallel.pagerank(g)[3] ≈ 0.318 atol = 0.001 23 | @test length(@inferred(Parallel.pagerank(g))) == nv(g) 24 | @test_throws ErrorException Parallel.pagerank(g, 2) 25 | @test_throws ErrorException Parallel.pagerank(g, α, 2) 26 | @test isapprox(Parallel.pagerank(g, α), dense_pagerank_solver(g, α), atol=0.001) 27 | end 28 | 29 | for g in testgraphs(g6) 30 | @test length(@inferred(Parallel.pagerank(g))) == nv(g) 31 | @test_throws ErrorException Parallel.pagerank(g, 2) 32 | @test_throws ErrorException Parallel.pagerank(g, α, 2) 33 | @test isapprox(Parallel.pagerank(g, α), dense_pagerank_solver(g, α), atol=0.001) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/community/clique_percolation.jl: -------------------------------------------------------------------------------- 1 | """ 2 | clique_percolation(g, k=3) 3 | 4 | Community detection using the clique percolation algorithm. Communities are potentionally overlapping. 5 | Return a vector of vectors `c` such that `c[i]` is the set of vertices in community `i`. 6 | The parameter `k` defines the size of the clique to use in percolation. 7 | 8 | ### References 9 | - [Palla G, Derenyi I, Farkas I J, et al.] (https://www.nature.com/articles/nature03607) 10 | 11 | # Examples 12 | ```jldoctest 13 | julia> using LightGraphs 14 | 15 | julia> clique_percolation(clique_graph(3, 2)) 16 | 2-element Array{BitSet,1}: 17 | BitSet([4, 5, 6]) 18 | BitSet([1, 2, 3]) 19 | 20 | julia> clique_percolation(clique_graph(3, 2), k=2) 21 | 1-element Array{BitSet,1}: 22 | BitSet([1, 2, 3, 4, 5, 6]) 23 | 24 | julia> clique_percolation(clique_graph(3, 2), k=4) 25 | 0-element Array{BitSet,1} 26 | ``` 27 | """ 28 | function clique_percolation end 29 | 30 | @traitfn function clique_percolation(g::::(!IsDirected); k = 3) 31 | kcliques = filter(x->length(x)>=k, maximal_cliques(g)) 32 | nc = length(kcliques) 33 | # graph with nodes represent k-cliques 34 | h = Graph(nc) 35 | # vector for counting common nodes between two cliques efficiently 36 | x = falses(nv(g)) 37 | for i = 1:nc 38 | x[kcliques[i]] .= true 39 | for j = i+1:nc 40 | sum(x[kcliques[j]]) >= k-1 && add_edge!(h, i, j) 41 | end 42 | # reset status 43 | x[kcliques[i]] .= false 44 | end 45 | components = connected_components(h) 46 | communities = [BitSet() for i=1:length(components)] 47 | for (i,component) in enumerate(components) 48 | push!(communities[i], vcat(kcliques[component]...)...) 49 | end 50 | return communities 51 | end 52 | -------------------------------------------------------------------------------- /src/Parallel/centrality/stress.jl: -------------------------------------------------------------------------------- 1 | stress_centrality(g::AbstractGraph, vs::AbstractVector=vertices(g); parallel=:distributed) = 2 | parallel == :distributed ? distr_stress_centrality(g, vs) : threaded_stress_centrality(g, vs) 3 | 4 | stress_centrality(g::AbstractGraph, k::Integer; parallel=:distributed) = 5 | parallel == :distributed ? distr_stress_centrality(g, sample(vertices(g), k)) : 6 | threaded_stress_centrality(g, sample(vertices(g), k)) 7 | 8 | function distr_stress_centrality(g::AbstractGraph, 9 | vs::AbstractVector=vertices(g))::Vector{Int64} 10 | 11 | n_v = nv(g) 12 | k = length(vs) 13 | isdir = is_directed(g) 14 | 15 | # Parallel reduction 16 | stress = @distributed (+) for s in vs 17 | temp_stress = zeros(Int64, n_v) 18 | if degree(g, s) > 0 # this might be 1? 19 | state = LightGraphs.dijkstra_shortest_paths(g, s; allpaths=true, trackvertices=true) 20 | LightGraphs._stress_accumulate_basic!(temp_stress, state, g, s) 21 | end 22 | temp_stress 23 | end 24 | return stress 25 | end 26 | 27 | function threaded_stress_centrality(g::AbstractGraph, 28 | vs::AbstractVector=vertices(g))::Vector{Int64} 29 | 30 | n_v = nv(g) 31 | k = length(vs) 32 | isdir = is_directed(g) 33 | 34 | # Parallel reduction 35 | local_stress = [zeros(Int, n_v) for _ in 1:nthreads()] 36 | 37 | Base.Threads.@threads for s in vs 38 | if degree(g, s) > 0 # this might be 1? 39 | state = LightGraphs.dijkstra_shortest_paths(g, s; allpaths=true, trackvertices=true) 40 | LightGraphs._stress_accumulate_basic!(local_stress[Base.Threads.threadid()], state, g, s) 41 | end 42 | end 43 | return reduce(+, local_stress) 44 | end 45 | -------------------------------------------------------------------------------- /test/degeneracy.jl: -------------------------------------------------------------------------------- 1 | @testset "Decomposition" begin 2 | d = loadgraph(joinpath(testdir, "testdata", "graph-decomposition.jgz")) 3 | 4 | @testset "$g" for g in testgraphs(d) 5 | corenum = @inferred(core_number(g)) 6 | @test corenum == [3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0] 7 | 8 | @testset "k-core" begin 9 | @test @inferred(k_core(g)) == k_core(g, corenum=corenum) == [1:8;] 10 | @test @inferred(k_core(g, 2)) == k_core(g, 2, corenum=corenum) == [1:16;] 11 | @test length(k_core(g, 4)) == 0 12 | end 13 | 14 | @testset "k-shell" begin 15 | @test @inferred(k_shell(g)) == k_shell(g, corenum=corenum) == [1:8;] 16 | @test @inferred(k_shell(g, 2)) == k_shell(g, 2, corenum=corenum) == [9:16;] 17 | @test length(k_shell(g, 4)) == 0 18 | end 19 | 20 | @testset "k-crust" begin 21 | @test @inferred(k_crust(g)) == k_crust(g, corenum=corenum) == [9:21;] 22 | @test @inferred(k_crust(g, 2)) == k_crust(g, 2, corenum=corenum) == [9:21;] 23 | @test @inferred(k_crust(g, 4, corenum=corenum)) == [1:21;] 24 | end 25 | 26 | @testset "k-corona" begin 27 | @test @inferred(k_corona(g, 1)) == k_corona(g, 1, corenum=corenum) == [17:20;] 28 | @test @inferred(k_corona(g, 2)) == [10, 12, 13, 14, 15, 16] 29 | end 30 | 31 | @testset "errors" begin 32 | add_edge!(g, 1, 1) 33 | @test_throws ArgumentError k_core(g) 34 | @test_throws ArgumentError k_shell(g) 35 | @test_throws ArgumentError k_crust(g) 36 | @test_throws ArgumentError k_corona(g, 1) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/interface.jl: -------------------------------------------------------------------------------- 1 | mutable struct DummyGraph <: AbstractGraph{Int} end 2 | mutable struct DummyDiGraph <: AbstractGraph{Int} end 3 | mutable struct DummyEdge <: AbstractEdge{Int} end 4 | 5 | @testset "Interface" begin 6 | dummygraph = DummyGraph() 7 | dummydigraph = DummyDiGraph() 8 | dummyedge = DummyEdge() 9 | 10 | @test_throws LightGraphs.NotImplementedError is_directed(DummyGraph) 11 | @test_throws LightGraphs.NotImplementedError zero(DummyGraph) 12 | 13 | for edgefun in [src, dst, Pair, Tuple, reverse] 14 | @test_throws LightGraphs.NotImplementedError edgefun(dummyedge) 15 | end 16 | 17 | for edgefun2edges in [==] 18 | @test_throws LightGraphs.NotImplementedError edgefun2edges(dummyedge, dummyedge) 19 | end 20 | 21 | for graphfunbasic in [ 22 | nv, ne, vertices, edges, is_directed, 23 | edgetype, eltype 24 | ] 25 | @test_throws LightGraphs.NotImplementedError graphfunbasic(dummygraph) 26 | end 27 | 28 | for graphfun1int in [ 29 | has_vertex, inneighbors, outneighbors 30 | ] 31 | @test_throws LightGraphs.NotImplementedError graphfun1int(dummygraph, 1) 32 | end 33 | for graphfunedge in [ 34 | has_edge, 35 | ] 36 | @test_throws LightGraphs.NotImplementedError graphfunedge(dummygraph, dummyedge) 37 | @test_throws LightGraphs.NotImplementedError graphfunedge(dummygraph, 1, 2) 38 | end 39 | 40 | # Implementation error 41 | impl_error = LightGraphs.NotImplementedError(edges) 42 | @test impl_error isa LightGraphs.NotImplementedError{typeof(edges)} 43 | io = IOBuffer() 44 | Base.showerror(io, impl_error) 45 | @test String(take!(io)) == "method $edges not implemented." 46 | 47 | end # testset 48 | -------------------------------------------------------------------------------- /benchmark/parallel/egonets.jl: -------------------------------------------------------------------------------- 1 | using LightGraphs 2 | using BenchmarkTools 3 | @show Threads.nthreads() 4 | 5 | @benchgroup "parallel" begin 6 | @benchgroup "egonet" begin 7 | function vertex_function(g::Graph, i::Int) 8 | a = 0 9 | for u in neighbors(g, i) 10 | a += degree(g, u) 11 | end 12 | return a 13 | end 14 | 15 | function twohop(g::Graph, i::Int) 16 | a = 0 17 | for u in neighbors(g, i) 18 | for v in neighbors(g, u) 19 | a += degree(g, v) 20 | end 21 | end 22 | return a 23 | end 24 | 25 | 26 | function mapvertices(f, g::Graph) 27 | n = nv(g) 28 | a = zeros(Int, n) 29 | Threads.@threads for i in 1:n 30 | a[i] = f(g, i) 31 | end 32 | return a 33 | end 34 | 35 | function mapvertices_single(f, g) 36 | n = nv(g) 37 | a = zeros(Int, n) 38 | for i in 1:n 39 | a[i] = f(g, i) 40 | end 41 | return a 42 | end 43 | 44 | function comparison(f, g) 45 | println("Mulithreaded on $(Threads.nthreads())") 46 | b1 = @benchmark mapvertices($f, $g) 47 | println(b1) 48 | 49 | println("singlethreaded") 50 | b2 = @benchmark mapvertices_single($f, $g) 51 | println(b2) 52 | println("done") 53 | end 54 | 55 | nv_ = 10000 56 | g = SimpleGraph(nv_, 64 * nv_) 57 | f = vertex_function 58 | println(g) 59 | 60 | comparison(vertex_function, g) 61 | comparison(twohop, g) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /src/Parallel/shortestpaths/floyd-warshall.jl: -------------------------------------------------------------------------------- 1 | #Helper function used due to performance bug in @threads. 2 | function _loopbody!( 3 | pivot::U, 4 | nvg::U, 5 | dists::Matrix{T}, 6 | parents::Matrix{U} 7 | ) where T<:Real where U<:Integer 8 | # Relax dists[u, v] = min(dists[u, v], dists[u, pivot]+dists[pivot, v]) for all u, v 9 | @inbounds @threads for v in one(U):nvg 10 | d = dists[pivot, v] 11 | if d != typemax(T) && v != pivot 12 | p = parents[pivot, v] 13 | @inbounds for u in one(U):nvg 14 | ans = (dists[u, pivot] == typemax(T) || u == pivot ? typemax(T) : dists[u, pivot] + d) 15 | if dists[u, v] > ans 16 | dists[u, v] = ans 17 | parents[u, v] = p 18 | end 19 | end 20 | end 21 | end 22 | end 23 | 24 | function floyd_warshall_shortest_paths( 25 | g::AbstractGraph{U}, 26 | distmx::AbstractMatrix{T}=weights(g) 27 | ) where T<:Real where U<:Integer 28 | nvg = nv(g) 29 | dists = fill(typemax(T), (Int(nvg), Int(nvg))) 30 | parents = zeros(U, (Int(nvg), Int(nvg))) 31 | 32 | for v in 1:nvg 33 | dists[v, v] = zero(T) 34 | end 35 | undirected = !is_directed(g) 36 | for e in edges(g) 37 | u = src(e) 38 | v = dst(e) 39 | 40 | d = distmx[u, v] 41 | 42 | dists[u, v] = min(d, dists[u, v]) 43 | parents[u, v] = u 44 | if undirected 45 | dists[v, u] = min(d, dists[v, u]) 46 | parents[v, u] = v 47 | end 48 | end 49 | 50 | for pivot in vertices(g) 51 | _loopbody!(pivot, nvg, dists, parents) #Due to bug in @threads 52 | end 53 | fws = FloydWarshallState(dists, parents) 54 | return fws 55 | end 56 | 57 | -------------------------------------------------------------------------------- /test/edit_distance.jl: -------------------------------------------------------------------------------- 1 | @testset "Edit distance" begin 2 | 3 | gtri = random_regular_graph(3, 2) 4 | gquad = random_regular_graph(4, 2) 5 | gpent = random_regular_graph(5, 2) 6 | 7 | @testset "edit_distance $triangle, $quadrangle, $pentagon" for triangle in testgraphs(gtri), quadrangle in testgraphs(gquad), pentagon in testgraphs(gpent) 8 | d, λ = @inferred(edit_distance(triangle, quadrangle, subst_cost=MinkowskiCost(1:3, 1:4))) 9 | @test d == 1.0 10 | @test λ == Tuple[(1, 1), (2, 2), (3, 3), (0, 4)] 11 | 12 | d, λ = @inferred(edit_distance(quadrangle, triangle, subst_cost=MinkowskiCost(1:4, 1:3))) 13 | @test d == 1.0 14 | @test λ == Tuple[(1, 1), (2, 2), (3, 3), (4, 0)] 15 | 16 | d, λ = @inferred(edit_distance(triangle, pentagon, subst_cost=MinkowskiCost(1:3, 1:5))) 17 | @test d == 2.0 18 | @test λ == Tuple[(1, 1), (2, 2), (3, 3), (0, 4), (0, 5)] 19 | 20 | d, λ = @inferred(edit_distance(pentagon, triangle, subst_cost=MinkowskiCost(1:5, 1:3))) 21 | @test d == 2.0 22 | @test λ == Tuple[(1, 1), (2, 2), (3, 3), (4, 0), (5, 0)] 23 | end 24 | 25 | @testset "Minkowski cost / bounded Minkowski" begin 26 | cost = @inferred(MinkowskiCost(1:3, 1:3)) 27 | bcost = @inferred(BoundedMinkowskiCost(1:3, 1:3)) 28 | for i = 1:3 29 | @test cost(i, i) == 0. 30 | @test bcost(i, i) == 2 / 3 31 | end 32 | end 33 | 34 | g1c = complete_graph(4) 35 | g2c = complete_graph(4) 36 | rem_edge!(g2c, 1, 2) 37 | @testset "edit_distance $g1, $g2" for g1 in testgraphs(g1c), g2 in testgraphs(g2c) 38 | d, λ = @inferred(edit_distance(g1, g2)) 39 | @test d == 2.0 40 | @test λ == Tuple[(1, 1), (2, 2), (3, 3), (4, 4)] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /src/Experimental/ShortestPaths/desopo-pape.jl: -------------------------------------------------------------------------------- 1 | """ 2 | struct DEsopoPape <: ShortestPathAlgorithm 3 | 4 | The structure used to configure and specify that [`shortest_paths`](@ref) 5 | should use the [D'Esopo-Pape algorithm](http://web.mit.edu/dimitrib/www/SLF.pdf). 6 | No fields are specified or required. 7 | 8 | ### Implementation Notes 9 | `DEsopoPape` supports the following shortest-path functionality: 10 | - non-negative distance matrices / weights 11 | - all destinations 12 | """ 13 | struct DEsopoPape <: ShortestPathAlgorithm end 14 | struct DEsopoPapeResult{T, U<:Integer} <: ShortestPathResult 15 | parents::Vector{U} 16 | dists::Vector{T} 17 | end 18 | 19 | function shortest_paths(g::AbstractGraph, src::Integer, distmx::AbstractMatrix, ::DEsopoPape) 20 | T = eltype(distmx) 21 | U = eltype(g) 22 | nvg = nv(g) 23 | (src in 1:nvg) || throw(DomainError(src, "src should be in between 1 and $nvg")) 24 | dists = fill(typemax(T), nvg) 25 | parents = zeros(U, nvg) 26 | state = Vector{Int8}() 27 | state = fill(Int8(2), nvg) 28 | q = U[src] 29 | @inbounds dists[src] = 0 30 | 31 | @inbounds while !isempty(q) 32 | u = popfirst!(q) 33 | state[u] = 0 34 | 35 | for v in outneighbors(g, u) 36 | alt = dists[u] + distmx[u, v] 37 | if (dists[v] > alt) 38 | dists[v] = alt 39 | parents[v] = u 40 | 41 | if state[v] == 2 42 | state[v] = 1 43 | push!(q, v) 44 | elseif state[v] == 0 45 | state[v] = 1 46 | pushfirst!(q, v) 47 | end 48 | end 49 | end 50 | end 51 | 52 | return DEsopoPapeResult{T, U}(parents, dists) 53 | end 54 | -------------------------------------------------------------------------------- /src/Parallel/centrality/pagerank.jl: -------------------------------------------------------------------------------- 1 | function pagerank( 2 | g::AbstractGraph{U}, 3 | α=0.85, 4 | n=100::Integer, 5 | ϵ=1.0e-6 6 | ) where U <: Integer 7 | 8 | # indegree(g, v) is estimated run-time to iterate over inneighbors(g, v) 9 | partitions = LightGraphs.optimal_contiguous_partition(indegree(g), nthreads(), nv(g)) 10 | 11 | α_div_outdegree = Vector{Float64}(undef,nv(g)) 12 | dangling_nodes = Vector{U}() 13 | @inbounds for v in vertices(g) 14 | if outdegree(g, v) == 0 15 | push!(dangling_nodes, v) 16 | end 17 | α_div_outdegree[v] = (α/outdegree(g, v)) 18 | end 19 | 20 | nvg = Int(nv(g)) 21 | # solution vector and temporary vector 22 | x = fill(1.0 / nvg, nvg) 23 | xlast = copy(x) 24 | @inbounds for _ in 1:n 25 | dangling_sum = 0.0 26 | for v in dangling_nodes 27 | dangling_sum += x[v] 28 | end 29 | # flow from teleprotation 30 | y = (1 - α + α * dangling_sum) * (1.0 / nvg) 31 | xlast .= y 32 | # flow from edges 33 | let x = x 34 | @threads for v_set in partitions 35 | for v in v_set 36 | for u in inneighbors(g, v) 37 | xlast[v] += (x[u] * α_div_outdegree[u]) 38 | end 39 | end 40 | end 41 | end 42 | 43 | # l1 change in solution convergence criterion 44 | err = 0.0 45 | for v in vertices(g) 46 | err += abs(xlast[v] - x[v]) 47 | x[v] = xlast[v] 48 | end 49 | if (err < nvg * ϵ) 50 | return x 51 | end 52 | end 53 | error("Pagerank did not converge after $n iterations.") # TODO 0.7: change to InexactError with appropriate msg. 54 | end 55 | -------------------------------------------------------------------------------- /src/dominatingset/minimal_dom_set.jl: -------------------------------------------------------------------------------- 1 | export MinimalDominatingSet 2 | 3 | struct MinimalDominatingSet end 4 | 5 | """ 6 | dominating_set(g, MinimalDominatingSet(); seed=-1) 7 | 8 | Find a set of vertices that consitute a dominating set (all vertices in `g` are either adjacent to a vertex 9 | in the set or is a vertex in the set) and it is not possible to delete a vertex from the set 10 | without sacrificing the dominating property. 11 | 12 | ### Implementation Notes 13 | Initially, every vertex is in the dominating set. 14 | In some random order, we check if the removal of a vertex from the set will destroy the 15 | dominating property. If no, the vertex is removed from the dominating set. 16 | 17 | ### Performance 18 | Runtime: ``\\mathcal{O}(|V|+|E|)`` 19 | Memory: ``\\mathcal{O}(|V|)`` 20 | 21 | ### Optional Arguments 22 | - If `seed >= 0`, a random generator is seeded with this value. 23 | """ 24 | function dominating_set( 25 | g::AbstractGraph{T}, 26 | alg::MinimalDominatingSet; 27 | seed::Int=-1 28 | ) where T <: Integer 29 | 30 | nvg = nv(g) 31 | in_dom_set = trues(nvg) 32 | length_ds = Int(nvg) 33 | dom_degree = degree(g) 34 | @inbounds @simd for v in vertices(g) 35 | dom_degree[v] -= (has_edge(g, v, v) ? 1 : 0) 36 | end 37 | 38 | for v in randperm(getRNG(seed), nvg) 39 | (dom_degree[v] == 0) && continue #It is not adjacent to any dominating vertex 40 | #Check if any vertex is depending on v to be dominated 41 | dependent = findfirst(u -> !in_dom_set[u] && dom_degree[u] <= 1, neighbors(g, v)) 42 | 43 | (dependent != nothing) && continue 44 | in_dom_set[v] = false 45 | length_ds -= 1 46 | dom_degree[neighbors(g, v)] .-= 1 47 | end 48 | 49 | return LightGraphs.findall!(in_dom_set, Vector{T}(undef, length_ds)) 50 | end 51 | -------------------------------------------------------------------------------- /test/steinertree/steiner_tree.jl: -------------------------------------------------------------------------------- 1 | @testset "Steiner Tree" begin 2 | 3 | function sum_weight(g::AbstractGraph{<:Integer}, distmx::AbstractMatrix{<:Integer} = weights(g)) 4 | sum_wt = zero(eltype(g)) 5 | for e in edges(g) 6 | sum_wt += distmx[src(e), dst(e)] 7 | end 8 | return sum_wt 9 | end 10 | 11 | approx_factor(t::Integer) = 2-2/t 12 | 13 | g3 = star_graph(5) 14 | for g in testgraphs(g3) 15 | g_st = @inferred(steiner_tree(g, [2, 5])) 16 | @test ne(g_st) == 2 # [Edge(2, 1), Edge(1, 5)] 17 | 18 | g_copy = SimpleGraph(g) 19 | LightGraphs.filter_non_term_leaves!(g_copy, [2, 5]) 20 | @test ne(g_copy) == 2 # [Edge(2, 1), Edge(1, 5)] 21 | end 22 | 23 | g4 = path_graph(11) 24 | for g in testgraphs(g4) 25 | g_st = @inferred(steiner_tree(g, [4, 8])) 26 | @test ne(g_st) == 4 27 | 28 | g_copy = SimpleGraph(g) 29 | LightGraphs.filter_non_term_leaves!(g_copy, [4, 8]) 30 | @test ne(g_copy) == 4 31 | end 32 | 33 | 34 | g5 = grid([5, 5]) 35 | for g in testgraphs(g5) 36 | g_st = @inferred(steiner_tree(g, [3, 11, 15, 23])) 37 | @test sum_weight(g_st) == 8 38 | end 39 | 40 | d = [0 2 3 4 5 41 | 2 0 60 80 1 42 | 3 60 0 120 150 43 | 4 80 120 0 200 44 | 5 1 150 200 0] 45 | 46 | g6 = complete_graph(5) 47 | 48 | for g in testgraphs(g6) 49 | g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) 50 | @test sum_weight(g_st, d) <= approx_factor(3)*(1+2+4) 51 | end 52 | 53 | d[2, 5] = d[5, 2] = 100 54 | 55 | for g in testgraphs(g6) 56 | g_st = @inferred(steiner_tree(g, [2, 4, 5], d)) 57 | @test sum_weight(g_st, d) <= approx_factor(3)*(2+4+5) 58 | end 59 | 60 | 61 | 62 | end 63 | -------------------------------------------------------------------------------- /test/shortestpaths/floyd-warshall.jl: -------------------------------------------------------------------------------- 1 | @testset "Floyd Warshall" begin 2 | g3 = path_graph(5) 3 | d = [0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0] 4 | for g in testgraphs(g3) 5 | z = @inferred(floyd_warshall_shortest_paths(g, d)) 6 | @test z.dists[3, :][:] == [7, 6, 0, 11, 27] 7 | @test z.parents[3, :][:] == [2, 3, 0, 3, 4] 8 | 9 | @test @inferred(enumerate_paths(z))[2][2] == [] 10 | @test @inferred(enumerate_paths(z))[2][4] == enumerate_paths(z, 2)[4] == enumerate_paths(z, 2, 4) == [2, 3, 4] 11 | end 12 | g4 = path_digraph(4) 13 | d = ones(4, 4) 14 | for g in testdigraphs(g4) 15 | z = @inferred(floyd_warshall_shortest_paths(g, d)) 16 | @test length(enumerate_paths(z, 4, 3)) == 0 17 | @test length(enumerate_paths(z, 4, 1)) == 0 18 | @test length(enumerate_paths(z, 2, 3)) == 2 19 | end 20 | 21 | g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) 22 | d = [0 3 8 0 -4; 0 0 0 1 7; 0 4 0 0 0; 2 0 -5 0 0; 0 0 0 6 0] 23 | for g in testdigraphs(g5) 24 | z = @inferred(floyd_warshall_shortest_paths(g, d)) 25 | @test z.dists == [0 1 -3 2 -4; 3 0 -4 1 -1; 7 4 0 5 3; 2 -1 -5 0 -2; 8 5 1 6 0] 26 | end 27 | 28 | @testset "enumerate_paths infinite loop bug" begin 29 | g = SimpleGraph(2) 30 | add_edge!(g, 1, 2) 31 | add_edge!(g, 2, 2) 32 | @test enumerate_paths(floyd_warshall_shortest_paths(g)) == 33 | Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] 34 | 35 | g = SimpleDiGraph(2) 36 | add_edge!(g, 1, 1) 37 | add_edge!(g, 1, 2) 38 | add_edge!(g, 2, 1) 39 | add_edge!(g, 2, 2) 40 | @test enumerate_paths(floyd_warshall_shortest_paths(g)) == 41 | Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/linalg/LinAlg.jl: -------------------------------------------------------------------------------- 1 | module LinAlg 2 | 3 | using ArnoldiMethod 4 | 5 | using SimpleTraits 6 | using SparseArrays: SparseMatrixCSC 7 | import SparseArrays: blockdiag, sparse 8 | using LinearAlgebra: I, Symmetric, diagm, dot, eigen, eigvals, norm, rmul!, tril, triu 9 | import LinearAlgebra: Diagonal, diag, issymmetric, mul! 10 | 11 | using ..LightGraphs 12 | 13 | 14 | import Base: convert, size, eltype, ndims, ==, *, length 15 | 16 | export convert, 17 | SparseMatrix, 18 | GraphMatrix, 19 | Adjacency, 20 | adjacency, 21 | Laplacian, 22 | CombinatorialAdjacency, 23 | CombinatorialLaplacian, 24 | NormalizedAdjacency, 25 | NormalizedLaplacian, 26 | StochasticAdjacency, 27 | StochasticLaplacian, 28 | AveragingAdjacency, 29 | AveragingLaplacian, 30 | PunchedAdjacency, 31 | Noop, 32 | diag, 33 | degrees, 34 | symmetrize, 35 | prescalefactor, 36 | postscalefactor, 37 | perron, 38 | 39 | non_backtracking_matrix, 40 | Nonbacktracking, 41 | contract!, 42 | contract, 43 | 44 | adjacency_matrix, 45 | laplacian_matrix, 46 | incidence_matrix, 47 | adjacency_spectrum, 48 | laplacian_spectrum, 49 | coo_sparse, 50 | spectral_distance, 51 | eigs 52 | 53 | function eigs(A; kwargs...) 54 | schr =partialschur(A; kwargs...) 55 | vals, vectors = partialeigen(schr[1]) 56 | reved = (kwargs[:which] == LR() || kwargs[:which] == LM()) 57 | k::Int = get(kwargs, :nev, length(vals)) 58 | k = min(k, length(vals)) 59 | perm = collect(1:k) 60 | if vals[1] isa(Real) 61 | perm = sortperm(vals, rev=reved) 62 | perm = perm[1:k] 63 | end 64 | λ = vals[perm] 65 | Q = vectors[:, perm] 66 | return λ, Q 67 | end 68 | 69 | include("./graphmatrices.jl") 70 | include("./spectral.jl") 71 | include("./nonbacktracking.jl") 72 | 73 | 74 | end 75 | 76 | -------------------------------------------------------------------------------- /src/cycles/limited_length.jl: -------------------------------------------------------------------------------- 1 | """ 2 | simplecycles_limited_length(g, n, ceiling=10^6) 3 | 4 | Compute and return at most `ceiling` cycles of length at most `n` of 5 | the given graph. Both directed and undirected graphs are supported. 6 | 7 | ### Performance 8 | The number of cycles grows very fast with the number of vertices and 9 | the allowed length of the cycles. This function is intended for 10 | finding short cycles. If you want to find cycles of any length in a 11 | directed graph, [`simplecycles`](@ref) or [`simplecycles_iter`](@ref) may be more 12 | efficient. 13 | """ 14 | function simplecycles_limited_length(graph::AbstractGraph{T}, n::Int, 15 | ceiling = 10^6) where {T} 16 | cycles = Vector{Vector{T}}() 17 | n < 1 && return cycles 18 | cycle = Vector{T}(undef, n) 19 | @inbounds for v in vertices(graph) 20 | cycle[1] = v 21 | simplecycles_limited_length!(graph, n, ceiling, cycles, cycle, 1) 22 | length(cycles) >= ceiling && break 23 | end 24 | return cycles 25 | end 26 | 27 | function simplecycles_limited_length!(graph, n, ceiling, cycles, cycle, i) 28 | length(cycles) >= ceiling && return 29 | for v in outneighbors(graph, cycle[i]) 30 | if v == cycle[1] 31 | push!(cycles, cycle[1:i]) 32 | elseif (i < n 33 | && v > cycle[1] 34 | && !repeated_vertex(v, cycle, 2, i)) 35 | cycle[i + 1] = v 36 | simplecycles_limited_length!(graph, n, ceiling, cycles, cycle, i + 1) 37 | end 38 | end 39 | end 40 | 41 | # Note: Doing the same thing as `@views v in cycle[n1:n2]`, but until 42 | # views are completely allocation free this can be expected to be 43 | # faster. 44 | function repeated_vertex(v, cycle, n1, n2) 45 | for k = n1:n2 46 | cycle[k] == v && return true 47 | end 48 | return false 49 | end 50 | -------------------------------------------------------------------------------- /docs/src/generators.md: -------------------------------------------------------------------------------- 1 | # Making and Modifying Graphs 2 | 3 | *LightGraphs.jl* provides a number of methods for creating a graph object, 4 | including tools for building and modifying graph objects, a wide array of graph 5 | generator functions, and the ability to read and write graphs from files 6 | (using [GraphIO.jl](https://github.com/JuliaGraphs/GraphIO.jl)). 7 | 8 | 9 | ## Modifying graphs 10 | 11 | *LightGraphs.jl* offers a range of tools for modifying graphs, including: 12 | 13 | ```@docs 14 | SimpleGraph 15 | SimpleGraphFromIterator 16 | SimpleDiGraph 17 | SimpleDiGraphFromIterator 18 | Edge 19 | add_edge! 20 | rem_edge! 21 | add_vertex! 22 | add_vertices! 23 | rem_vertex! 24 | zero 25 | ``` 26 | 27 | In addition to these core functions, more advanced operators can be found in [Operators](@ref). 28 | 29 | ## Graph Generators 30 | 31 | *LightGraphs.jl* implements numerous graph generators, including random graph generators, constructors for classic graphs, numerous small graphs with familiar topologies, and random and static graphs embedded in Euclidean space. 32 | 33 | ```@index 34 | Modules = [LightGraphs] 35 | Pages = ["generators.md"] 36 | ``` 37 | 38 | ## Datasets 39 | 40 | Other notorious graphs and integration with the `MatrixDepot.jl` package are available in the `Datasets` submodule of the companion package [LightGraphsExtras.jl](https://github.com/JuliaGraphs/LightGraphsExtras.jl). Selected graphs from the [Stanford Large Network Dataset Collection](https://snap.stanford.edu/data/index.html) may be found in the [SNAPDatasets.jl](https://github.com/JuliaGraphs/SNAPDatasets.jl) package. 41 | 42 | ### All Generators 43 | 44 | ```@autodocs 45 | Modules = [LightGraphs.SimpleGraphs] 46 | Pages = [ 47 | "generators/randgraphs.jl", 48 | "generators/staticgraphs.jl", 49 | "generators/smallgraphs.jl", 50 | "generators/euclideangraphs.jl"] 51 | Private = false 52 | ``` 53 | -------------------------------------------------------------------------------- /src/centrality/katz.jl: -------------------------------------------------------------------------------- 1 | # Inspiration for this code came from EvolvingGraphs: 2 | # https://github.com/weijianzhang/EvolvingGraphs.jl 3 | # 4 | # The EvolvingGraphs.jl package is licensed under the MIT "Expat" License: 5 | # Copyright (c) 2015: Weijian Zhang. 6 | # Permission is hereby granted, free of charge, to any person obtaining a 7 | # copy of this software and associated documentation files (the "Software"), 8 | # to deal in the Software without restriction, including without limitation 9 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | # and/or sell copies of the Software, and to permit persons to whom the 11 | # Software is furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included 14 | # in all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.# 19 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 22 | # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | """ 25 | katz_centrality(g, α=0.3) 26 | 27 | Calculate the [Katz centrality](https://en.wikipedia.org/wiki/Katz_centrality) 28 | of the graph `g` optionally parameterized by `α`. Return a vector representing 29 | the centrality calculated for each node in `g`. 30 | """ 31 | function katz_centrality(g::AbstractGraph, α::Real=0.3) 32 | nvg = nv(g) 33 | v = ones(Float64, nvg) 34 | spI = sparse(one(Float64) * I, nvg, nvg) 35 | A = adjacency_matrix(g, Bool; dir=:in) 36 | v = (spI - α * A) \ v 37 | v /= norm(v) 38 | return v 39 | end 40 | -------------------------------------------------------------------------------- /src/graphcut/karger_min_cut.jl: -------------------------------------------------------------------------------- 1 | """ 2 | karger_min_cut(g) 3 | 4 | Perform [Karger Minimum Cut](https://en.wikipedia.org/wiki/Karger%27s_algorithm) 5 | to find the minimum cut of graph `g` with some probability of success. 6 | A cut is a partition of `vertices(g)` into two non-empty sets. 7 | The size of a cut is the number of edges crossing the two non-empty sets. 8 | 9 | ### Implementation Notes 10 | The cut is represented by an integer array. 11 | If `cut[v] == 1` then `v` is in the first non-empty set. 12 | If `cut[v] == 2` then `v` is in the second non-empty set. 13 | `cut[1] = 1`. 14 | 15 | If |V| < 2 then `cut[v] = 0` for all `v`. 16 | 17 | ### Performance 18 | Runtime: O(|E|) 19 | Memory: O(|E|) 20 | """ 21 | function karger_min_cut(g::AbstractGraph{T}) where T <: Integer 22 | 23 | nvg = nv(g) 24 | nvg < 2 && return zeros(Int, nvg) 25 | nvg == 2 && return [1, 2] 26 | 27 | connected_vs = IntDisjointSets(nvg) 28 | num_components = nvg 29 | 30 | for e in shuffle(collect(edges(g))) 31 | s = src(e) 32 | d = dst(e) 33 | if !in_same_set(connected_vs, s, d) 34 | union!(connected_vs, s, d) 35 | num_components -= one(T) 36 | (num_components <= 2) && break 37 | end 38 | end 39 | 40 | return [(in_same_set(connected_vs, one(T), v) ? 1 : 2) for v in vertices(g)] 41 | end 42 | 43 | """ 44 | karger_cut_cost(g, cut) 45 | 46 | Find the number of crossing edges in a cut of graph `g` where the cut is represented 47 | by the integer array, `cut`. 48 | """ 49 | karger_cut_cost(g::AbstractGraph{T}, cut::Vector{<:Integer}) where T <: Integer = 50 | count((e::Edge{T})->cut[src(e)] != cut[dst(e)], edges(g)) 51 | 52 | """ 53 | karger_cut_edges(g, cut) 54 | 55 | Find the crossing edges in a cut of graph `g` where the cut is represented 56 | by the integer array, `cut`. 57 | """ 58 | karger_cut_edges(g::AbstractGraph{T}, cut::Vector{<:Integer}) where T <: Integer = 59 | [e for e in edges(g) if cut[src(e)] != cut[dst(e)]] 60 | -------------------------------------------------------------------------------- /src/centrality/pagerank.jl: -------------------------------------------------------------------------------- 1 | # Parts of this code were taken / derived from NetworkX. See LICENSE for 2 | # licensing details. 3 | 4 | using Base.Threads 5 | 6 | """ 7 | pagerank(g, α=0.85, n=100, ϵ=1.0e-6) 8 | 9 | Calculate the [PageRank](https://en.wikipedia.org/wiki/PageRank) of the 10 | graph `g` parameterized by damping factor `α`, number of iterations 11 | `n`, and convergence threshold `ϵ`. Return a vector representing the 12 | centrality calculated for each node in `g`, or an error if convergence 13 | is not reached within `n` iterations. 14 | """ 15 | function pagerank( 16 | g::AbstractGraph{U}, 17 | α=0.85, 18 | n::Integer=100, 19 | ϵ=1.0e-6 20 | ) where U <: Integer 21 | α_div_outdegree = Vector{Float64}(undef,nv(g)) 22 | dangling_nodes = Vector{U}() 23 | for v in vertices(g) 24 | if outdegree(g, v) == 0 25 | push!(dangling_nodes, v) 26 | end 27 | α_div_outdegree[v] = (α/outdegree(g, v)) 28 | end 29 | N = Int(nv(g)) 30 | # solution vector and temporary vector 31 | x = fill(1.0 / N, N) 32 | xlast = copy(x) 33 | for _ in 1:n 34 | dangling_sum = 0.0 35 | for v in dangling_nodes 36 | dangling_sum += x[v] 37 | end 38 | # flow from teleprotation 39 | for v in vertices(g) 40 | xlast[v] = (1 - α + α * dangling_sum) * (1.0 / N) 41 | end 42 | # flow from edges 43 | 44 | for v in vertices(g) 45 | for u in inneighbors(g, v) 46 | xlast[v] += (x[u] * α_div_outdegree[u]) 47 | end 48 | end 49 | # l1 change in solution convergence criterion 50 | err = 0.0 51 | for v in vertices(g) 52 | err += abs(xlast[v] - x[v]) 53 | x[v] = xlast[v] 54 | end 55 | if (err < N * ϵ) 56 | return x 57 | end 58 | end 59 | error("Pagerank did not converge after $n iterations.") # TODO 0.7: change to InexactError with appropriate msg. 60 | end 61 | -------------------------------------------------------------------------------- /src/SimpleGraphs/generators/deprecations.jl: -------------------------------------------------------------------------------- 1 | @deprecate BullGraph bull_graph 2 | @deprecate ChvatalGraph chvatal_graph 3 | @deprecate CubicalGraph cubical_graph 4 | @deprecate DesarguesGraph desargues_graph 5 | @deprecate DiamondGraph diamond_graph 6 | @deprecate DodecahedralGraph dodecahedral_graph 7 | @deprecate FruchtGraph frucht_graph 8 | @deprecate HeawoodGraph heawood_graph 9 | @deprecate HouseGraph house_graph 10 | @deprecate HouseXGraph house_x_graph 11 | @deprecate IcosahedralGraph icosahedral_graph 12 | @deprecate KarateGraph karate_graph 13 | @deprecate KrackhardtKiteGraph krackhardt_kite_graph 14 | @deprecate MoebiusKantorGraph moebius_kantor_graph 15 | @deprecate OctahedralGraph octahedral_graph 16 | @deprecate PappusGraph pappus_graph 17 | @deprecate PetersenGraph petersen_graph 18 | @deprecate SedgewickMazeGraph sedgewick_maze_graph 19 | @deprecate TetrahedralGraph tetrahedral_graph 20 | @deprecate TruncatedCubeGraph truncated_cube_graph 21 | @deprecate TruncatedTetrahedronGraph truncated_tetrahedron_graph 22 | @deprecate TruncatedTetrahedronDiGraph truncated_tetrahedron_digraph 23 | @deprecate TutteGraph tutte_graph 24 | @deprecate CompleteGraph complete_graph 25 | @deprecate CompleteBipartiteGraph complete_bipartite_graph 26 | @deprecate CompleteMultipartiteGraph complete_multipartite_graph 27 | @deprecate TuranGraph turan_graph 28 | @deprecate CompleteDiGraph complete_digraph 29 | @deprecate StarGraph star_graph 30 | @deprecate StarDigraph star_digraph 31 | @deprecate PathGraph path_graph 32 | @deprecate PathDiGraph path_digraph 33 | @deprecate CycleGraph cycle_graph 34 | @deprecate CycleDiGraph cycle_digraph 35 | @deprecate WheelGraph wheel_graph 36 | @deprecate WheelDiGraph wheel_digraph 37 | @deprecate Grid grid 38 | @deprecate BinaryTree binary_tree 39 | @deprecate Doublebinary_tree double_binary_tree 40 | @deprecate RoachGraph roach_graph 41 | @deprecate CliqueGraph clique_graph 42 | @deprecate LadderGraph ladder_graph 43 | @deprecate Circularladder_graph circular_ladder_graph 44 | @deprecate BarbellGraph barbell_graph 45 | @deprecate LollipopGraph lollipop_graph 46 | -------------------------------------------------------------------------------- /test/persistence/persistence.jl: -------------------------------------------------------------------------------- 1 | @testset "Persistence" begin 2 | @testset "Errors" begin 3 | @test_throws LightGraphs.NotImplementedError LightGraphs._NI("Not implemented") 4 | end 5 | 6 | pdict = loadgraphs(joinpath(testdir, "testdata", "tutte-pathdigraph.jgz")) 7 | p1 = pdict["Tutte"] 8 | p2 = pdict["pathdigraph"] 9 | @testset "LGFormat simple load" begin 10 | @test (ne(p2), nv(p2)) == (9, 10) 11 | end 12 | g3 = path_graph(5) 13 | 14 | (f, fio) = mktemp() 15 | # test :lg 16 | @testset "LGFormat save single graph" begin 17 | @test savegraph(f, p1) == 1 18 | @test_deprecated r"Saving compressed graphs is no longer supported" savegraph(f, p1; compress=true) 19 | @test savegraph(f, p1) == 1 20 | @test_deprecated r"Saving compressed graphs is no longer supported" savegraph(f, p1, LGFormat(); compress=true) 21 | @test_logs (:info,r"Note: the `compress` keyword is no longer supported in LightGraphs") savegraph(f, p1; compress=false) 22 | @test savegraph(f, p1, LGFormat()) == 1 23 | @test savegraph(f, p2) == 1 24 | end 25 | 26 | g2 = loadgraph(f) 27 | h2 = loadgraph(f, LGFormat()) 28 | j2 = loadgraph(f, "graph") 29 | @testset "LGFormat load single graph" begin 30 | @test g2 == h2 == j2 31 | @test (ne(g2), nv(g2)) == (9, 10) 32 | end 33 | 34 | @testset "LGFormat save - content check" begin 35 | (f, fio) = mktemp() 36 | @test length(sprint(savegraph, p1, LGFormat())) == 421 37 | @test length(sprint(savegraph, p2, LGFormat())) == 70 38 | gs = loadgraph(joinpath(testdir, "testdata", "tutte-pathdigraph.jgz"), "pathdigraph") 39 | @test gs == p2 40 | @test_throws ArgumentError loadgraph(joinpath(testdir, "testdata", "tutte-pathdigraph.jgz"), "badname") 41 | end 42 | 43 | @testset "LGFormat save multiple graphs" begin 44 | d = Dict{String,AbstractGraph}("p1" => p1, "p2" => p2) 45 | @test savegraph(f, d) == 2 46 | end 47 | 48 | close(fio) 49 | rm(f) 50 | end 51 | -------------------------------------------------------------------------------- /test/simplegraphs/generators/smallgraphs.jl: -------------------------------------------------------------------------------- 1 | @testset "Small graphs" begin 2 | @testset "by string" begin 3 | g = smallgraph("diamondgraph") 4 | @test nv(g) == 4 && ne(g) == 5 5 | 6 | g = smallgraph("diamond") 7 | @test nv(g) == 4 && ne(g) == 5 8 | end 9 | 10 | smallgraphs = [ 11 | (:diamond , 4, 5, false), 12 | (:bull , 5, 5, false), 13 | (:chvatal , 12, 24, false), 14 | (:cubical , 8, 12, false), 15 | (:desargues , 20, 30, false), 16 | (:dodecahedral , 20, 30, false), 17 | (:frucht , 12, 18, false), 18 | (:heawood , 14, 21, false), 19 | (:house , 5,6, false), 20 | (:housex , 5, 8, false), 21 | (:icosahedral , 12, 30, false), 22 | (:krackhardtkite , 10, 18, false), 23 | (:moebiuskantor , 16, 24, false), 24 | (:octahedral , 6, 12, false), 25 | (:pappus , 18, 27, false), 26 | (:petersen , 10, 15, false), 27 | (:sedgewickmaze , 8, 10, false), 28 | (:tetrahedral , 4, 6, false), 29 | (:truncatedcube , 24, 36, false), 30 | (:truncatedtetrahedron , 12, 18, false), 31 | (:truncatedtetrahedron_dir , 12, 18, true), 32 | (:tutte , 46, 69, false) 33 | ] 34 | 35 | @testset "$(s[1])" for s in smallgraphs 36 | g = smallgraph(s[1]) 37 | (nvg, neg, dir) = s[2:4] 38 | @test nv(g) == nvg 39 | @test ne(g) == neg 40 | @test is_directed(g) == dir 41 | end 42 | 43 | @testset "karate" begin 44 | g = smallgraph(:karate) 45 | degree_sequence = sort([16, 9, 10, 6, 3, 4, 4, 4, 5, 2, 3, 1, 2, 5, 2, 2, 2, 46 | 2, 2, 3, 2, 2, 2, 5, 3, 3, 2, 4, 3, 4, 4, 6, 12, 17]) 47 | @test nv(g) == 34 && ne(g) == 78 && sort(degree(g)) == degree_sequence 48 | end 49 | @testset "nonexistent graph" begin 50 | @test_throws ArgumentError g = smallgraph(:nonexistent) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/Parallel/centrality/closeness.jl: -------------------------------------------------------------------------------- 1 | closeness_centrality(g::AbstractGraph, distmx::AbstractMatrix=weights(g); normalize=true, parallel=:distributed) = 2 | parallel == :distributed ? distr_closeness_centrality(g, distmx; normalize=normalize) : 3 | threaded_closeness_centrality(g, distmx; normalize=normalize) 4 | 5 | function distr_closeness_centrality(g::AbstractGraph, 6 | distmx::AbstractMatrix=weights(g); 7 | normalize=true)::Vector{Float64} 8 | 9 | n_v = Int(nv(g)) 10 | closeness = SharedVector{Float64}(n_v) 11 | 12 | @sync @distributed for u in vertices(g) 13 | if degree(g, u) == 0 # no need to do Dijkstra here 14 | closeness[u] = 0.0 15 | else 16 | d = LightGraphs.dijkstra_shortest_paths(g, u, distmx).dists 17 | δ = filter(x -> x != typemax(x), d) 18 | σ = sum(δ) 19 | l = length(δ) - 1 20 | if σ > 0 21 | closeness[u] = l / σ 22 | if normalize 23 | n = l * 1.0 / (n_v - 1) 24 | closeness[u] *= n 25 | end 26 | end 27 | end 28 | end 29 | return sdata(closeness) 30 | end 31 | 32 | function threaded_closeness_centrality(g::AbstractGraph, 33 | distmx::AbstractMatrix=weights(g); 34 | normalize=true)::Vector{Float64} 35 | 36 | n_v = Int(nv(g)) 37 | closeness = Vector{Float64}(undef, n_v) 38 | 39 | Base.Threads.@threads for u in vertices(g) 40 | if degree(g, u) == 0 # no need to do Dijkstra here 41 | closeness[u] = 0.0 42 | else 43 | d = LightGraphs.dijkstra_shortest_paths(g, u, distmx).dists 44 | δ = filter(x -> x != typemax(x), d) 45 | σ = sum(δ) 46 | l = length(δ) - 1 47 | if σ > 0 48 | closeness[u] = l / σ 49 | if normalize 50 | n = l * 1.0 / (n_v - 1) 51 | closeness[u] *= n 52 | end 53 | end 54 | end 55 | end 56 | return closeness 57 | end 58 | -------------------------------------------------------------------------------- /test/shortestpaths/bellman-ford.jl: -------------------------------------------------------------------------------- 1 | @testset "Bellman Ford" begin 2 | 3 | g4 = path_digraph(5) 4 | 5 | d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) 6 | d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) 7 | for g in testdigraphs(g4) 8 | y = @inferred(bellman_ford_shortest_paths(g, 2, d1)) 9 | z = @inferred(bellman_ford_shortest_paths(g, 2, d2)) 10 | @test y.dists == z.dists == [Inf, 0, 6, 17, 33] 11 | @test @inferred(enumerate_paths(z))[2] == [] 12 | @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z, 4) == [2, 3, 4] 13 | @test @inferred(!has_negative_edge_cycle(g)) 14 | @test @inferred(!has_negative_edge_cycle(g, d1)) 15 | 16 | 17 | y = @inferred(bellman_ford_shortest_paths(g, 2, d1)) 18 | z = @inferred(bellman_ford_shortest_paths(g, 2, d2)) 19 | @test y.dists == z.dists == [Inf, 0, 6, 17, 33] 20 | @test @inferred(enumerate_paths(z))[2] == [] 21 | @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z, 4) == [2, 3, 4] 22 | @test @inferred(!has_negative_edge_cycle(g)) 23 | z = @inferred(bellman_ford_shortest_paths(g, 2)) 24 | @test z.dists == [typemax(Int), 0, 1, 2, 3] 25 | end 26 | 27 | # Negative Cycle 28 | gx = complete_graph(3) 29 | for g in testgraphs(gx) 30 | d = [1 -3 1; -3 1 1; 1 1 1] 31 | @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) 32 | @test has_negative_edge_cycle(g, d) 33 | 34 | d = [1 -1 1; -1 1 1; 1 1 1] 35 | @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) 36 | @test has_negative_edge_cycle(g, d) 37 | end 38 | 39 | # Negative cycle of length 3 in graph of diameter 4 40 | gx = complete_graph(4) 41 | d = [1 -1 1 1; 1 1 1 -1; 1 1 1 1; 1 1 1 1] 42 | for g in testgraphs(gx) 43 | @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) 44 | @test has_negative_edge_cycle(g, d) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/parallel/shortestpaths/bellman-ford.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Bellman Ford" begin 2 | g4 = path_digraph(5) 3 | 4 | d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) 5 | d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) 6 | for g in testdigraphs(g4) 7 | y = @inferred(Parallel.bellman_ford_shortest_paths(g, [2], d1)) 8 | z = @inferred(Parallel.bellman_ford_shortest_paths(g, [2], d2)) 9 | @test y.dists == z.dists == [Inf, 0, 6, 17, 33] 10 | @test @inferred(enumerate_paths(z))[2] == [] 11 | @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z, 4) == [2, 3, 4] 12 | @test @inferred(!Parallel.has_negative_edge_cycle(g, d1)) 13 | 14 | 15 | y = @inferred(Parallel.bellman_ford_shortest_paths(g, [2], d1)) 16 | z = @inferred(Parallel.bellman_ford_shortest_paths(g, [2], d2)) 17 | @test y.dists == z.dists == [Inf, 0, 6, 17, 33] 18 | @test @inferred(enumerate_paths(z))[2] == [] 19 | @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z, 4) == [2, 3, 4] 20 | z = @inferred(Parallel.bellman_ford_shortest_paths(g, [2])) 21 | @test z.dists == [typemax(Int), 0, 1, 2, 3] 22 | end 23 | 24 | # Negative Cycle 25 | gx = complete_graph(3) 26 | for g in testgraphs(gx) 27 | d = [1 -3 1; -3 1 1; 1 1 1] 28 | @test_throws LightGraphs.NegativeCycleError Parallel.bellman_ford_shortest_paths(g, [1], d) 29 | @test Parallel.has_negative_edge_cycle(g, d) 30 | 31 | d = [1 -1 1; -1 1 1; 1 1 1] 32 | @test_throws LightGraphs.NegativeCycleError Parallel.bellman_ford_shortest_paths(g, [1], d) 33 | @test Parallel.has_negative_edge_cycle(g, d) 34 | end 35 | 36 | # Negative cycle of length 3 in graph of diameter 4 37 | gx = complete_graph(4) 38 | d = [1 -1 1 1; 1 1 1 -1; 1 1 1 1; 1 1 1 1] 39 | for g in testgraphs(gx) 40 | @test_throws LightGraphs.NegativeCycleError Parallel.bellman_ford_shortest_paths(g, [1], d) 41 | @test Parallel.has_negative_edge_cycle(g, d) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/Parallel/utils.jl: -------------------------------------------------------------------------------- 1 | """ 2 | generate_reduce(g, gen_func, comp, reps; parallel=:threads) 3 | 4 | Compute `gen_func(g)` `reps` times and return the instance `best` for which 5 | `comp(best, v)` is true where `v` is all the other instances of `gen_func(g)`. 6 | 7 | For example, `comp(x, y) = length(x) < length(y) ? x : y` then instance with the smallest 8 | length will be returned. 9 | """ 10 | generate_reduce(g::AbstractGraph{T}, gen_func::Function, comp::Comp, reps::Integer; parallel=:threads) where {T<:Integer,Comp} = 11 | parallel == :threads ? threaded_generate_reduce(g, gen_func, comp, reps) : distr_generate_reduce(g, gen_func, comp, reps) 12 | 13 | """ 14 | distr_generate_min_set(g, gen_func, comp, reps) 15 | 16 | Distributed implementation of [`LightGraphs.generate_reduce`](@ref). 17 | """ 18 | function distr_generate_reduce( 19 | g::AbstractGraph{T}, 20 | gen_func::Function, 21 | comp::Comp, 22 | reps::Integer 23 | ) where {T<:Integer,Comp} 24 | # Type assert required for type stability 25 | min_set::Vector{T} = @distributed ((x, y)->comp(x, y) ? x : y) for _ in 1:reps 26 | gen_func(g) 27 | end 28 | return min_set 29 | end 30 | 31 | """ 32 | threaded_generate_reduce(g, gen_func, comp reps) 33 | 34 | Multi-threaded implementation of [`LightGraphs.generate_reduce`](@ref). 35 | """ 36 | function threaded_generate_reduce( 37 | g::AbstractGraph{T}, 38 | gen_func::Function, 39 | comp::Comp, 40 | reps::Integer 41 | ) where {T<:Integer,Comp} 42 | n_t = Base.Threads.nthreads() 43 | is_undef = ones(Bool, n_t) 44 | min_set = [Vector{T}() for _ in 1:n_t] 45 | Base.Threads.@threads for _ in 1:reps 46 | t = Base.Threads.threadid() 47 | next_set = gen_func(g) 48 | if is_undef[t] || comp(next_set, min_set[t]) 49 | min_set[t] = next_set 50 | is_undef[t] = false 51 | end 52 | end 53 | 54 | min_ind = 0 55 | for i in filter((j)->!is_undef[j], 1:n_t) 56 | if min_ind == 0 || comp(min_set[i], min_set[min_ind]) 57 | min_ind = i 58 | end 59 | end 60 | 61 | return min_set[min_ind] 62 | end 63 | -------------------------------------------------------------------------------- /src/Parallel/shortestpaths/bellman-ford.jl: -------------------------------------------------------------------------------- 1 | function bellman_ford_shortest_paths( 2 | g::AbstractGraph{U}, 3 | sources::AbstractVector{<:Integer}, 4 | distmx::AbstractMatrix{T}=weights(g) 5 | ) where T<:Real where U<:Integer 6 | 7 | nvg = nv(g) 8 | active = Set{U}() 9 | sizehint!(active, nv(g)) 10 | for s in sources 11 | union!(active, outneighbors(g, s)) 12 | end 13 | dists = fill(typemax(T), nvg) 14 | parents = zeros(U, nvg) 15 | dists[sources] .= 0 16 | 17 | for i in one(U):nvg 18 | _loop_body!(g, distmx, dists, parents, active) 19 | 20 | isempty(active) && break 21 | end 22 | 23 | isempty(active) || throw(LightGraphs.NegativeCycleError()) 24 | return BellmanFordState(parents, dists) 25 | end 26 | 27 | #Helper function used due to performance bug in @threads. 28 | function _loop_body!( 29 | g::AbstractGraph{U}, 30 | distmx::AbstractMatrix{T}, 31 | dists::Vector{T}, 32 | parents::Vector{U}, 33 | active::Set{U} 34 | ) where T<:Real where U<:Integer 35 | 36 | 37 | prev_dists = deepcopy(dists) 38 | 39 | tmp_active = collect(active) 40 | @threads for v in tmp_active 41 | prev_dist_vertex = prev_dists[v] 42 | for u in inneighbors(g, v) 43 | relax_dist = (prev_dists[u] == typemax(T) ? typemax(T) : prev_dists[u] + distmx[u,v]) 44 | if prev_dist_vertex > relax_dist 45 | prev_dist_vertex = relax_dist 46 | parents[v] = u 47 | end 48 | end 49 | dists[v] = prev_dist_vertex 50 | end 51 | 52 | empty!(active) 53 | for v in vertices(g) 54 | if dists[v] < prev_dists[v] 55 | union!(active, outneighbors(g, v)) 56 | end 57 | end 58 | end 59 | 60 | function has_negative_edge_cycle( 61 | g::AbstractGraph{U}, 62 | distmx::AbstractMatrix{T} 63 | ) where T<:Real where U<:Integer 64 | try 65 | Parallel.bellman_ford_shortest_paths(g, vertices(g), distmx) 66 | catch e 67 | isa(e, LightGraphs.NegativeCycleError) && return true 68 | end 69 | return false 70 | end 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/shortestpaths/desopo-pape.jl: -------------------------------------------------------------------------------- 1 | """ 2 | struct DEposoPapeState{T, U} 3 | 4 | An [`AbstractPathState`](@ref) designed for D`Esopo-Pape shortest-path calculations. 5 | """ 6 | struct DEsopoPapeState{T <:Real, U <: Integer} <: AbstractPathState 7 | parents::Vector{U} 8 | dists::Vector{T} 9 | end 10 | 11 | """ 12 | desopo_pape_shortest_paths(g, src, distmx=weights(g)) 13 | 14 | Compute shortest paths between a source `src` and all 15 | other nodes in graph `g` using the [D'Esopo-Pape algorithm](http://web.mit.edu/dimitrib/www/SLF.pdf). 16 | Return a [`LightGraphs.DEsopoPapeState`](@ref) with relevant traversal information. 17 | 18 | # Examples 19 | ```jldoctest 20 | julia> using LightGraphs 21 | 22 | julia> ds = desopo_pape_shortest_paths(cycle_graph(5), 2); 23 | 24 | julia> ds.dists 25 | 5-element Array{Int64,1}: 26 | 1 27 | 0 28 | 1 29 | 2 30 | 2 31 | 32 | julia> ds = desopo_pape_shortest_paths(path_graph(5), 2); 33 | 34 | julia> ds.dists 35 | 5-element Array{Int64,1}: 36 | 1 37 | 0 38 | 1 39 | 2 40 | 3 41 | ``` 42 | """ 43 | function desopo_pape_shortest_paths(g::AbstractGraph, 44 | src::Integer, 45 | distmx::AbstractMatrix{T} = weights(g)) where T <: Real 46 | U = eltype(g) 47 | nvg = nv(g) 48 | (src in 1:nvg) || throw(DomainError(src, "src should be in between 1 and $nvg")) 49 | dists = fill(typemax(T), nvg) 50 | parents = zeros(U, nvg) 51 | state = Vector{Int8}() 52 | state = fill(Int8(2), nvg) 53 | q = U[src] 54 | @inbounds dists[src] = 0 55 | 56 | @inbounds while !isempty(q) 57 | u = popfirst!(q) 58 | state[u] = 0 59 | 60 | for v in outneighbors(g, u) 61 | alt = dists[u] + distmx[u, v] 62 | if (dists[v] > alt) 63 | dists[v] = alt 64 | parents[v] = u 65 | 66 | if state[v] == 2 67 | state[v] = 1 68 | push!(q, v) 69 | elseif state[v] == 0 70 | state[v] = 1 71 | pushfirst!(q, v) 72 | end 73 | end 74 | end 75 | end 76 | 77 | return DEsopoPapeState{T, U}(parents, dists) 78 | end 79 | -------------------------------------------------------------------------------- /test/parallel/traversals/bfs.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.BFS" begin 2 | @testset "Thread Queue" begin 3 | next = @inferred(Parallel.ThreadQueue(Int, 5)) # Initialize threadqueue 4 | @test isempty(next) == true 5 | push!(next, 1) 6 | @test next[1][] == 1 7 | @threads for i = 2:5 8 | push!(next, i) 9 | end 10 | @test Set([i[] for i in next[1:5]]) == Set([1, 2, 3, 4, 5]) 11 | first = popfirst!(next) 12 | @test first == 1 13 | end 14 | 15 | g5 = SimpleDiGraph(4) 16 | add_edge!(g5, 1, 2); add_edge!(g5, 2, 3); add_edge!(g5, 1, 3); add_edge!(g5, 3, 4) 17 | g6 = smallgraph(:house) 18 | 19 | for g in testdigraphs(g5) 20 | T = eltype(g) 21 | z = @inferred(Parallel.bfs_tree(g, T(1))) 22 | next = Parallel.ThreadQueue(T, nv(g)) # Initialize threadqueue 23 | parents = [Atomic{T}(0) for i = 1:nv(g)] # Create parents array 24 | Parallel.bfs_tree!(next, g, T(1), parents) 25 | t = [i[] for i in parents] 26 | @test t == [T(1), T(1), T(1), T(3)] 27 | @test nv(z) == T(4) && ne(z) == T(3) && !has_edge(z, 2, 3) 28 | end 29 | 30 | function istree(parents::Vector{Atomic{T}}, maxdepth, n::T) where T<:Integer 31 | flag = true 32 | for i in one(T):n 33 | s = i 34 | depth = 0 35 | while parents[s][] > 0 && parents[s][] != s 36 | s = parents[s][] 37 | depth += 1 38 | if depth > maxdepth 39 | return false 40 | end 41 | end 42 | end 43 | return flag 44 | end 45 | 46 | for g in testgraphs(g6) 47 | n = nv(g) 48 | T = eltype(g) 49 | next = Parallel.ThreadQueue(eltype(g), nv(g)) # Initialize threadqueue 50 | parents = [Atomic{T}(0) for i = 1:nv(g)] # Create parents array 51 | @test length(next.data) == n 52 | @inferred(Parallel.bfs_tree!(next, g, T(1), parents)) 53 | @test istree(parents, n, n) 54 | p = [i[] for i in parents] 55 | t = LightGraphs.tree(p) 56 | @test is_directed(t) 57 | @test typeof(t) <: AbstractGraph 58 | @test ne(t) < nv(t) 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # LightGraphs 2 | 3 | The goal of *LightGraphs.jl* is to offer a performant platform for network and graph analysis in Julia. To this end, LightGraphs offers both (a) a set of simple, concrete graph implementations -- `SimpleGraph` (for undirected graphs) and `SimpleDiGraph` (for directed graphs), and (b) an API for the development of more sophisticated graph implementations under the `AbstractGraph` type. 4 | 5 | As such, *LightGraphs.jl* is the central package of the JuliaGraphs ecosystem. Additional functionality like advanced IO and file formats, weighted graphs, property graphs, and optimization related functions can be found in the following packages: 6 | * [LightGraphsExtras.jl](https://github.com/JuliaGraphs/LightGraphsExtras.jl): extra functions for graph analysis. 7 | * [MetaGraphs.jl](https://github.com/JuliaGraphs/MetaGraphs.jl): graphs with associated meta-data. 8 | * [SimpleWeightedGraphs.jl](https://github.com/JuliaGraphs/SimpleWeightedGraphs.jl): weighted graphs. 9 | * [GraphIO.jl](https://github.com/JuliaGraphs/GraphIO.jl): tools for importing and exporting graph objects using common file types like edgelists, GraphML, Pajek NET, and more. 10 | * [GraphDataFrameBridge.jl](https://github.com/JuliaGraphs/GraphDataFrameBridge.jl): Tools for converting edgelists stored in DataFrames into graphs (`MetaGraphs`, `MetaDiGraphs`). 11 | 12 | 13 | ## Basic library examples 14 | 15 | The *LightGraphs.jl* libraries includes numerous convenience functions for generating functions detailed in [Making and Modifying Graphs](@ref), such as `path_graph`, which makes a simple undirected [path graph](https://en.wikipedia.org/wiki/Path_graph) of a given length. Once created, these graphs can be easily interrogated and modified. 16 | 17 | ```julia 18 | julia> g = path_graph(6) 19 | 20 | # Number of vertices 21 | julia> nv(g) 22 | 23 | # Number of edges 24 | julia> ne(g) 25 | 26 | # Add an edge to make the path a loop 27 | julia> add_edge!(g, 1, 6) 28 | ``` 29 | 30 | For an overview of basic functions for interacting with graphs, check out [Accessing Graph Properties](@ref) and [Making and Modifying Graphs](@ref). Detailed tutorials may be found in the [JuliaGraphs Tutorial Notebooks](https://github.com/JuliaGraphs/JuliaGraphsTutorials) repository. 31 | -------------------------------------------------------------------------------- /docs/src/basicproperties.md: -------------------------------------------------------------------------------- 1 | # Accessing Graph Properties 2 | The following is an overview of functions for accessing graph properties. For functions that modify graphs, see [Making and Modifying Graphs](@ref). 3 | 4 | ## Graph Properties: 5 | 6 | - `nv`: Returns number of vertices in graph. 7 | - `ne`: Returns number of edges in graph. 8 | - `vertices`: Iterable object of all graph vertices. 9 | - `edges`: Iterable object of all graph edges. 10 | - `has_vertex`: Checks for whether graph includes a vertex. 11 | - `has_edge(g, s, d)`: Checks for whether graph includes an edge from a given source `s` to a given destination `d`. 12 | - `has_edge(g, e)` will return true if there is an edge in g that satisfies `e == f` for any `f ∈ edges(g)`. This is a strict equality test that may require all properties of `e` are the same. This definition of equality depends on the implementation. For testing whether an edge exists between two vertices `s,d` use `has_edge(g, s, d)`. 13 | Note: to use the `has_edge(g, e)` method safely, it is important to understand the conditions under which edges are equal to each other. These conditions are defined by the `has_edge(g::G,e)` method **as defined** by the graph type `G`. The default behavior is to check `has_edge(g,src(e),dst(e))`. This distinction exists to allow new graph types such as MetaGraphs or MultiGraphs to distinguish between edges with the same source and destination but potentially different properties. 14 | - `has_self_loops` Checks for self-loops. 15 | - `is_directed` Checks if graph is directed. 16 | - `eltype` Returns element type of graphs. 17 | 18 | ## Vertex Properties 19 | 20 | - `neighbors`: Return array of neighbors of a vertex. If graph is directed, output is equivalent of `outneighbors`. 21 | - `all_neighbors`: Returns array of all neighbors (both `inneighbors` and `outneighbors`). For undirected graphs, equivalent to `neighbors`. 22 | - `inneighbors`: Return array of in-neighbors. Equivalent to `neighbors` for undirected graphs. 23 | - `outneighbors`: Return array of out-neighbors. Equivalent to `neighbors` for undirected graphs. 24 | 25 | ## Edge Properties 26 | 27 | - `src`: Give source vertex of an edge. 28 | - `dst`: Give destination vertex of an edge. 29 | - `reverse`: Creates a new edge running in opposite direction of passed edge. 30 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter 2 | #include("../src/LightGraphs.jl") 3 | using LightGraphs 4 | 5 | # same for contributing and license 6 | cp(normpath(@__FILE__, "../../CONTRIBUTING.md"), normpath(@__FILE__, "../src/contributing.md"); force=true) 7 | cp(normpath(@__FILE__, "../../LICENSE.md"), normpath(@__FILE__, "../src/license.md"); force=true) 8 | 9 | makedocs( 10 | modules = [LightGraphs], 11 | format = Documenter.HTML(), 12 | sitename = "LightGraphs", 13 | doctest = false, 14 | pages = Any[ 15 | "Getting Started" => "index.md", 16 | "Choosing A Graph Type" => "graphtypes.md", 17 | "LightGraphs Types" => "types.md", 18 | "Accessing Properties" => "basicproperties.md", 19 | "Making and Modifying Graphs" => "generators.md", 20 | "Reading / Writing Graphs" => "persistence.md", 21 | "Operators" => "operators.md", 22 | "Plotting Graphs" => "plotting.md", 23 | "Path and Traversal" => "pathing.md", 24 | "Coloring" => "coloring.md", 25 | "Distance" => "distance.md", 26 | "Centrality Measures" => "centrality.md", 27 | "Linear Algebra" => "linalg.md", 28 | "Matching" => "matching.md", 29 | "Community Structures" => "community.md", 30 | "Degeneracy" => "degeneracy.md", 31 | "Integration with other packages" => "integration.md", 32 | "Experimental Functionality" => "experimental.md", 33 | "Parallel Algorithms" => "parallel.md", 34 | "Contributing" => "contributing.md", 35 | "Developer Notes" => "developing.md", 36 | "License Information" => "license.md", 37 | "Citing LightGraphs" => "citing.md" 38 | ] 39 | ) 40 | 41 | deploydocs( 42 | repo = "github.com/JuliaGraphs/LightGraphs.jl.git", 43 | target = "build", 44 | ) 45 | 46 | rm(normpath(@__FILE__, "../src/contributing.md")) 47 | rm(normpath(@__FILE__, "../src/license.md")) 48 | -------------------------------------------------------------------------------- /test/parallel/dominatingset/minimal_dom_set.jl: -------------------------------------------------------------------------------- 1 | @testset "Minimal Dominating Set" begin 2 | 3 | 4 | g0 = SimpleGraph(0) 5 | for g in testgraphs(g0) 6 | for parallel in [:threads, :distributed] 7 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 8 | @test isempty(d) 9 | end 10 | end 11 | 12 | g1 = SimpleGraph(1) 13 | for g in testgraphs(g1) 14 | for parallel in [:threads, :distributed] 15 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 16 | @test (d == [1,]) 17 | 18 | add_edge!(g, 1, 1) 19 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 20 | @test (d == [1,]) 21 | end 22 | end 23 | 24 | g3 = star_graph(5) 25 | for parallel in [:threads, :distributed] 26 | for g in testgraphs(g3) 27 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 28 | @test (length(d)== 1 || (length(d)== 4 && minimum(d) > 1 )) 29 | end 30 | end 31 | 32 | g4 = complete_graph(5) 33 | for parallel in [:threads, :distributed] 34 | for g in testgraphs(g4) 35 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 36 | @test length(d)== 1 #Exactly one vertex 37 | end 38 | end 39 | 40 | g5 = path_graph(4) 41 | for parallel in [:threads, :distributed] 42 | for g in testgraphs(g5) 43 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 44 | sort!(d) 45 | @test (d == [1, 2, 4] || d == [1, 3] || d == [1, 4] || d == [2, 3] || d == [2, 4]) 46 | end 47 | end 48 | 49 | add_edge!(g5, 2, 2) 50 | add_edge!(g5, 3, 3) 51 | for parallel in [:threads, :distributed] 52 | for g in testgraphs(g5) 53 | d = @inferred(Parallel.dominating_set(g, 4, MinimalDominatingSet(); parallel=parallel, seed=0)) 54 | sort!(d) 55 | @test (d == [1, 2, 4] || d == [1, 3] || d == [1, 4] || d == [2, 3] || d == [2, 4]) 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/parallel/vertexcover/random_vertex_cover.jl: -------------------------------------------------------------------------------- 1 | @testset "Parallel.Random Vertex Cover" begin 2 | 3 | g0 = SimpleGraph(0) 4 | for parallel in [:threads, :distributed] 5 | for g in testgraphs(g0) 6 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 7 | @test isempty(c) 8 | end 9 | end 10 | 11 | g1 = SimpleGraph(1) 12 | for parallel in [:threads, :distributed] 13 | for g in testgraphs(g1) 14 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 15 | @test isempty(c) 16 | end 17 | end 18 | 19 | add_edge!(g1, 1, 1) 20 | for parallel in [:threads, :distributed] 21 | for g in testgraphs(g1) 22 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 23 | @test c == [1,] 24 | end 25 | end 26 | 27 | g3 = star_graph(5) 28 | for parallel in [:threads, :distributed] 29 | for g in testgraphs(g3) 30 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 31 | @test (length(c)== 2 && (c[1] == 1 || c[2] == 1)) 32 | end 33 | end 34 | 35 | g4 = complete_graph(5) 36 | for parallel in [:threads, :distributed] 37 | for g in testgraphs(g4) 38 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 39 | @test length(c)== 4 #All except one vertex 40 | end 41 | end 42 | 43 | g5 = path_graph(5) 44 | for parallel in [:threads, :distributed] 45 | for g in testgraphs(g5) 46 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 47 | sort!(c) 48 | @test (c == [1, 2, 3, 4] || c == [1, 2, 4, 5] || c == [2, 3, 4, 5]) 49 | end 50 | end 51 | 52 | add_edge!(g5, 2, 2) 53 | add_edge!(g5, 3, 3) 54 | for parallel in [:threads, :distributed] 55 | for g in testgraphs(g5) 56 | c = @inferred(Parallel.vertex_cover(g, 4, RandomVertexCover(); parallel=parallel)) 57 | sort!(c) 58 | @test (c == [1, 2, 3, 4] || c == [1, 2, 3, 4, 5] || c == [2, 3, 4] || c == [2, 3, 4, 5]) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/parallel/independentset/maximal_ind_set.jl: -------------------------------------------------------------------------------- 1 | @testset "Maximal Independent Set" begin 2 | 3 | 4 | g0 = SimpleGraph(0) 5 | for parallel in [:threads, :distributed] 6 | for g in testgraphs(g0) 7 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 8 | @test isempty(z) 9 | end 10 | end 11 | 12 | g1 = SimpleGraph(1) 13 | for parallel in [:threads, :distributed] 14 | for g in testgraphs(g1) 15 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 16 | @test (z == [1,]) 17 | end 18 | end 19 | 20 | add_edge!(g1, 1, 1) 21 | for parallel in [:threads, :distributed] 22 | for g in testgraphs(g1) 23 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 24 | isempty(z) 25 | end 26 | end 27 | 28 | g3 = star_graph(5) 29 | for parallel in [:threads, :distributed] 30 | for g in testgraphs(g3) 31 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 32 | @test (length(z)== 1 || length(z)== 4) 33 | end 34 | end 35 | 36 | g4 = complete_graph(5) 37 | for parallel in [:threads, :distributed] 38 | for g in testgraphs(g4) 39 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 40 | @test length(z)== 1 #Exactly one vertex 41 | end 42 | end 43 | 44 | g5 = path_graph(5) 45 | for parallel in [:threads, :distributed] 46 | for g in testgraphs(g5) 47 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 48 | sort!(z) 49 | @test (z == [2, 4] || z == [2, 5] || z == [1, 3, 5] || z == [1, 4]) 50 | end 51 | end 52 | 53 | add_edge!(g5, 2, 2) 54 | add_edge!(g5, 3, 3) 55 | for parallel in [:threads, :distributed] 56 | for g in testgraphs(g5) 57 | z = @inferred(Parallel.independent_set(g, 4, MaximalIndependentSet(); parallel=parallel)) 58 | sort!(z) 59 | @test (z == [4,] || z == [5,] || z == [1, 5] || z == [1, 4]) 60 | end 61 | end 62 | end -------------------------------------------------------------------------------- /docs/src/plotting.md: -------------------------------------------------------------------------------- 1 | # Plotting Graphs 2 | 3 | *LightGraphs.jl* integrates with several other Julia packages for plotting. Here are a few examples. 4 | 5 | ## [GraphLayout.jl](https://github.com/IainNZ/GraphLayout.jl) 6 | 7 | This excellent graph visualization package can be used with *LightGraphs.jl* 8 | as follows: 9 | 10 | ```julia 11 | julia> g = wheel_graph(10); am = Matrix(adjacency_matrix(g)) 12 | julia> loc_x, loc_y = layout_spring_adj(am) 13 | julia> draw_layout_adj(am, loc_x, loc_y, filename="wheel10.svg") 14 | ``` 15 | 16 | producing a graph like this: 17 | 18 | ![Wheel Graph](https://cloud.githubusercontent.com/assets/941359/8960521/35582c1e-35c5-11e5-82d7-cd641dff424c.png) 19 | 20 | ## [TikzGraphs.jl](https://github.com/sisl/TikzGraphs.jl) 21 | 22 | Another nice graph visualization package. ([TikzPictures.jl](https://github.com/sisl/TikzPictures.jl) 23 | required to render/save): 24 | 25 | ```julia 26 | julia> g = wheel_graph(10); t = plot(g) 27 | 28 | julia> save(SVG("wheel10.svg"), t) 29 | ``` 30 | 31 | producing a graph like this: 32 | 33 | ![Wheel Graph](https://cloud.githubusercontent.com/assets/941359/8960499/17f703c0-35c5-11e5-935e-044be51bc531.png) 34 | 35 | ## [GraphPlot.jl](https://github.com/afternone/GraphPlot.jl) 36 | 37 | Another graph visualization package that is very simple to use. 38 | [Compose.jl](https://github.com/dcjones/Compose.jl) is required for most rendering functionality: 39 | 40 | ```julia 41 | julia> using GraphPlot, Compose 42 | 43 | julia> g = wheel_graph(10) 44 | 45 | julia> draw(PNG("/tmp/wheel10.png", 16cm, 16cm), gplot(g)) 46 | ``` 47 | 48 | 49 | ## [NetworkViz.jl](https://github.com/abhijithanilkumar/NetworkViz.jl) 50 | NetworkViz.jl is tightly coupled with *LightGraphs.jl*. Graphs can be visualized in 2D as well as 3D using [ThreeJS.jl](https://github.com/rohitvarkey/ThreeJS.jl) and [Escher.jl](https://github.com/shashi/Escher.jl). 51 | 52 | ```julia 53 | #Run this code in Escher 54 | 55 | using NetworkViz 56 | using LightGraphs 57 | 58 | main(window) = begin 59 | push!(window.assets, "widgets") 60 | push!(window.assets,("ThreeJS","threejs")) 61 | g = complete_graph(10) 62 | drawGraph(g) 63 | end 64 | ``` 65 | 66 | The above code produces the following output: 67 | 68 | ![alt tag](https://raw.githubusercontent.com/abhijithanilkumar/NetworkViz.jl/master/examples/networkviz.gif) 69 | -------------------------------------------------------------------------------- /test/cycles/basis.jl: -------------------------------------------------------------------------------- 1 | @testset "Cycle Basis" begin 2 | 3 | function evaluate(x,y) 4 | x_sorted = sort(sort.(x)) 5 | y_sorted = sort(sort.(y)) 6 | @test x_sorted == y_sorted 7 | end 8 | 9 | # No Edges 10 | ex = Graph(1) 11 | expected_cyclebasis = Array{Int64,1}[] 12 | @testset "no edges" for g in testgraphs(ex) 13 | ex_cyclebasis = @inferred cycle_basis(g) 14 | @test isempty(ex_cyclebasis) 15 | end 16 | 17 | # Only one self-edge 18 | elist = [(1,1)] 19 | ex = Graph(SimpleEdge.(elist)) 20 | expected_cyclebasis = Array{Int64,1}[[1]] 21 | @testset "one self-edge" for g in testgraphs(ex) 22 | ex_cyclebasis = cycle_basis(g) 23 | evaluate(ex_cyclebasis,expected_cyclebasis) 24 | end 25 | 26 | # Graph with one cycle 27 | elist = [(1,2),(2,3),(3,4),(4,1),(1,5)] 28 | ex = Graph(SimpleEdge.(elist)) 29 | expected_cyclebasis = Array{Int64,1}[ 30 | [1,2,3,4] ] 31 | @testset "one cycle" for g in testgraphs(ex) 32 | ex_cyclebasis = cycle_basis(g) 33 | evaluate(ex_cyclebasis,expected_cyclebasis) 34 | end 35 | 36 | # Graph with 2 of 3 cycles forming a basis 37 | elist = [(1,2),(1,3),(2,3),(2,4),(3,4)] 38 | ex = Graph(SimpleEdge.(elist)) 39 | expected_cyclebasis = Array{Int64,1}[ 40 | [2,3,4], 41 | [2,1,3] ] 42 | @testset "2 of 3 cycles w/ basis" for g in testgraphs(ex) 43 | ex_cyclebasis = cycle_basis(g) 44 | evaluate(ex_cyclebasis,expected_cyclebasis) 45 | end 46 | 47 | # Testing root argument 48 | elist = [(1,2),(1,3),(2,3),(2,4),(3,4),(1,5),(5,6),(6,4)] 49 | ex = Graph(SimpleEdge.(elist)) 50 | expected_cyclebasis = Array{Int64,1}[ 51 | [2, 4, 3], 52 | [1, 5, 6, 4, 3], 53 | [1, 2, 3] ] 54 | @testset "root argument" for g in testgraphs(ex) 55 | ex_cyclebasis = @inferred cycle_basis(g,3) 56 | evaluate(ex_cyclebasis,expected_cyclebasis) 57 | end 58 | 59 | @testset "two isolated cycles" begin 60 | ex = blockdiag(cycle_graph(3), cycle_graph(4)) 61 | expected_cyclebasis = [[1, 2, 3], [4, 5, 6, 7]] 62 | for g in testgraphs(ex) 63 | found_cyclebasis = @inferred cycle_basis(g) 64 | evaluate(expected_cyclebasis, found_cyclebasis) 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /src/dominatingset/degree_dom_set.jl: -------------------------------------------------------------------------------- 1 | export DegreeDominatingSet 2 | 3 | struct DegreeDominatingSet end 4 | 5 | """ 6 | update_dominated!(degree_queue, v, dominated, in_dom_set) 7 | 8 | Check if a vertex is already dominated. 9 | If not, make it dominated and update `degree_queue` by decreasing 10 | the priority of the vertices adjacent to `v` by 1. 11 | """ 12 | function update_dominated!( 13 | g::AbstractGraph{T}, 14 | degree_queue::PriorityQueue, 15 | v::Integer, 16 | dominated::BitArray{1}, 17 | in_dom_set::BitArray{1} 18 | ) where T <: Integer 19 | 20 | @inbounds if !dominated[v] 21 | dominated[v] = true 22 | if !in_dom_set[v] 23 | degree_queue[v] -= 1 24 | end 25 | @inbounds @simd for u in neighbors(g, v) 26 | if !in_dom_set[u] 27 | degree_queue[u] -= ifelse(in_dom_set[u], 0, 1) 28 | end 29 | end 30 | end 31 | end 32 | 33 | """ 34 | dominating_set(g, DegreeDominatingSet()) 35 | 36 | Obtain a [dominating set](https://en.wikipedia.org/wiki/Dominating_set) using a greedy heuristic. 37 | 38 | ### Implementation Notes 39 | A vertex is said to be dominated if it is in the dominating set or adjacent to a vertex 40 | in the dominating set. 41 | Initialise the dominating set to an empty set and iteratively choose the vertex that would 42 | dominate the most undominated vertices. 43 | 44 | ### Performance 45 | Runtime: ``\\mathcal{O}((|V|+|E|)*log(|V|))`` 46 | Memory: ``\\mathcal{O}(|V|)`` 47 | Approximation Factor: `ln(maximum(degree(g)))+2` 48 | """ 49 | function dominating_set( 50 | g::AbstractGraph{T}, 51 | alg::DegreeDominatingSet 52 | ) where T <: Integer 53 | 54 | nvg = nv(g) 55 | in_dom_set = falses(nvg) 56 | dominated = falses(nvg) 57 | degree_queue = PriorityQueue(Base.Order.Reverse, enumerate(degree(g) .+ 1)) 58 | length_ds = 0 59 | 60 | while !isempty(degree_queue) && peek(degree_queue)[2] > 0 61 | v = dequeue!(degree_queue) 62 | in_dom_set[v] = true 63 | length_ds += 1 64 | 65 | update_dominated!(g, degree_queue, v, dominated, in_dom_set) 66 | for u in neighbors(g, v) 67 | update_dominated!(g, degree_queue, u, dominated, in_dom_set) 68 | end 69 | end 70 | 71 | return LightGraphs.findall!(in_dom_set, Vector{T}(undef, length_ds)) 72 | end 73 | -------------------------------------------------------------------------------- /src/traversals/bipartition.jl: -------------------------------------------------------------------------------- 1 | """ 2 | bipartite_map(g) -> Vector{UInt8} 3 | 4 | For a bipartite graph `g`, return a vector `c` of size ``|V|`` containing 5 | the assignment of each vertex to one of the two sets (``c_i == 1`` or ``c_i == 2``). 6 | If `g` is not bipartite, return an empty vector. 7 | 8 | ### Implementation Notes 9 | Note that an empty vector does not necessarily indicate non-bipartiteness. 10 | An empty graph will return an empty vector but is bipartite. 11 | 12 | # Examples 13 | ```jldoctest 14 | julia> using LightGraphs 15 | 16 | julia> g = SimpleGraph(3); 17 | 18 | julia> bipartite_map(g) 19 | 3-element Array{UInt8,1}: 20 | 0x01 21 | 0x01 22 | 0x01 23 | 24 | julia> add_vertices!(g, 3); 25 | 26 | julia> add_edge!(g, 1, 2); 27 | 28 | julia> add_edge!(g, 2, 3); 29 | 30 | julia> bipartite_map(g) 31 | 3-element Array{UInt8,1}: 32 | 0x01 33 | 0x02 34 | 0x01 35 | ``` 36 | """ 37 | function bipartite_map(g::AbstractGraph{T}) where T 38 | nvg = nv(g) 39 | if !is_directed(g) 40 | ccs = filter(x -> length(x) >= 2, connected_components(g)) 41 | else 42 | ccs = filter(x -> length(x) >= 2, weakly_connected_components(g)) 43 | end 44 | seen = zeros(Bool, nvg) 45 | colors = zeros(Bool, nvg) 46 | for cc in ccs 47 | Q = Vector{T}() 48 | s = cc[1] 49 | push!(Q, s) 50 | bipartitemap = zeros(UInt8, nvg) 51 | while !isempty(Q) 52 | u = popfirst!(Q) 53 | for v in outneighbors(g, u) 54 | if !seen[v] 55 | colors[v] = !colors[u] 56 | push!(Q, v) 57 | seen[v] = true 58 | elseif colors[v] == colors[u] 59 | return Vector{UInt8}() 60 | end 61 | end 62 | end 63 | end 64 | return UInt8.(colors).+(one(UInt8)) 65 | end 66 | 67 | """ 68 | is_bipartite(g) 69 | 70 | Return `true` if graph `g` is [bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). 71 | 72 | # Examples 73 | ```jldoctest 74 | julia> using LightGraphs 75 | 76 | julia> g = SimpleGraph(3); 77 | 78 | julia> add_edge!(g, 1, 2); 79 | 80 | julia> add_edge!(g, 2, 3); 81 | 82 | julia> is_bipartite(g) 83 | true 84 | 85 | julia> add_edge!(g, 1, 3); 86 | 87 | julia> is_bipartite(g) 88 | false 89 | ``` 90 | """ 91 | is_bipartite(g::AbstractGraph) = length(bipartite_map(g)) == nv(g) 92 | -------------------------------------------------------------------------------- /test/cycles/limited_length.jl: -------------------------------------------------------------------------------- 1 | @testset "Limited Length Cycles" begin 2 | completedg = complete_digraph(4) 3 | pathdg = path_digraph(5) 4 | cycledg = cycle_digraph(5) 5 | 6 | @testset "complete digraph" for g in testgraphs(completedg) 7 | @test length(simplecycles_limited_length(g, 0)) == 0 8 | @test length(simplecycles_limited_length(g, 1)) == 0 9 | @test length(simplecycles_limited_length(g, 2)) == 6 10 | @test length(simplecycles_limited_length(g, 3)) == 14 11 | @test length(simplecycles_limited_length(g, 4)) == 20 12 | @test length(simplecycles_limited_length(g, 4, 10)) == 10 13 | @test length(simplecycles_limited_length(g, 4, typemax(Int))) == 20 14 | end 15 | 16 | @testset "path digraph" for g in testgraphs(pathdg) 17 | @test length(simplecycles_limited_length(g, 1)) == 0 18 | @test length(simplecycles_limited_length(g, 2)) == 0 19 | @test length(simplecycles_limited_length(g, 3)) == 0 20 | @test length(simplecycles_limited_length(g, 4)) == 0 21 | @test length(simplecycles_limited_length(g, 5)) == 0 22 | end 23 | 24 | @testset "cycle digraph" for g in testgraphs(cycledg) 25 | @test length(simplecycles_limited_length(g, 1)) == 0 26 | @test length(simplecycles_limited_length(g, 2)) == 0 27 | @test length(simplecycles_limited_length(g, 3)) == 0 28 | @test length(simplecycles_limited_length(g, 4)) == 0 29 | @test length(simplecycles_limited_length(g, 5)) == 1 30 | end 31 | 32 | @testset "self loops" begin 33 | selfloopg = DiGraph([ 34 | 0 1 0 0; 35 | 0 0 1 0; 36 | 1 0 1 0; 37 | 0 0 0 1; 38 | ]) 39 | cycles = simplecycles_limited_length(selfloopg, nv(selfloopg)) 40 | @test [3] in cycles 41 | @test [4] in cycles 42 | @test [1, 2, 3] in cycles || [2, 3, 1] in cycles || [3, 1, 2] in cycles 43 | @test length(cycles) == 3 44 | end 45 | 46 | @testset "octahedral graph" begin 47 | octag = smallgraph(:octahedral) 48 | octadg = DiGraph(octag) 49 | octalengths, _ = simplecycleslength(octadg) 50 | for k = 1:6 51 | @test sum(octalengths[1:k]) == length(simplecycles_limited_length(octag, k)) 52 | @test sum(octalengths[1:k]) == length(simplecycles_limited_length(octadg, k)) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/cycles/hawick-james.jl: -------------------------------------------------------------------------------- 1 | @testset "Hawick-James Cycles" begin 2 | 3 | # Simple graph gives expected circuits 4 | ex1 = SimpleDiGraph(5) 5 | add_edge!(ex1, 1, 2) 6 | add_edge!(ex1, 2, 3) 7 | add_edge!(ex1, 3, 4) 8 | add_edge!(ex1, 3, 5) 9 | add_edge!(ex1, 4, 5) 10 | add_edge!(ex1, 5, 2) 11 | 12 | @testset "subset" for g in testgraphs(ex1) 13 | expected_circuits = Vector{Int}[ 14 | [2, 3, 4, 5], 15 | [2, 3, 5] 16 | ] 17 | ex1_circuits = simplecycles_hawick_james(g) 18 | 19 | @test issubset(expected_circuits, ex1_circuits) 20 | @test issubset(ex1_circuits, expected_circuits) 21 | 22 | # Adding self-edges gives new circuits 23 | add_edge!(g, 1, 1) 24 | add_edge!(g, 3, 3) 25 | 26 | ex1_circuits_self = simplecycles_hawick_james(g) 27 | 28 | @test issubset(expected_circuits, ex1_circuits_self) 29 | @test [1] ∈ ex1_circuits_self && [3] ∈ ex1_circuits_self 30 | end 31 | 32 | # Path DiGraph 33 | ex2_size = 10 34 | ex2 = testgraphs(path_digraph(ex2_size)) 35 | @testset "empty" for g in ex2 36 | @test isempty(simplecycles_hawick_james(g)) 37 | end 38 | 39 | # Complete DiGraph 40 | ex3_size = 5 41 | ex3 = testgraphs(complete_digraph(ex3_size)) 42 | @testset "length" for g in ex3 43 | ex3_circuits = simplecycles_hawick_james(g) 44 | @test length(ex3_circuits) == length(unique(ex3_circuits)) 45 | end 46 | 47 | # Almost fully connected DiGraph 48 | ex4 = SimpleDiGraph(9) 49 | for (src, dest) in [(1, 2), (1, 5), (1, 7), (1, 8), (2, 9), (3, 4), (3, 6), 50 | (4, 5), (4, 7), (5, 6), (6, 7), (6, 8), (7, 9), (8, 9)] 51 | add_edge!(ex4, src, dest) 52 | add_edge!(ex4, dest, src) 53 | end 54 | @testset "membership" for g in testgraphs(ex4) 55 | ex4_output = simplecycles_hawick_james(g) 56 | @test [1, 2] ∈ ex4_output && [8, 9] ∈ ex4_output 57 | end 58 | 59 | # These test cases cover a bug that occurred in a previous version 60 | @testset "bugfix (unknown issue; PR#1007) ($seed)" for seed in [1, 2, 3], (n, k) in [(14, 18), (10, 22), (7, 16)] 61 | g = erdos_renyi(n, k, is_directed=true, seed=seed) 62 | cycles1 = simplecycles(g) 63 | cycles2 = simplecycles_hawick_james(g) 64 | foreach(sort!, cycles1) 65 | foreach(sort!, cycles2) 66 | sort!(cycles1) 67 | sort!(cycles2) 68 | @test cycles1 == cycles2 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /src/cycles/basis.jl: -------------------------------------------------------------------------------- 1 | # Code in this file inspired by NetworkX. 2 | 3 | """ 4 | cycle_basis(g, root=nothing) 5 | 6 | Return a list of cycles which form a basis for cycles of graph `g`, optionally starting at node `root`. 7 | 8 | A basis for cycles of a network is a minimal collection of 9 | cycles such that any cycle in the network can be written 10 | as a sum of cycles in the basis. Here summation of cycles 11 | is defined as "exclusive or" of the edges. Cycle bases are 12 | useful, e.g. when deriving equations for electric circuits 13 | using Kirchhoff's Laws. 14 | 15 | # Examples 16 | ```jldoctest 17 | julia> elist = [(1,2),(2,3),(2,4),(3,4),(4,1),(1,5)]; 18 | 19 | julia> g = SimpleGraph(SimpleEdge.(elist)); 20 | 21 | julia> cycle_basis(g) 22 | 2-element Array{Array{Int64,1},1}: 23 | [2, 3, 4] 24 | [2, 1, 3] 25 | ``` 26 | 27 | ### References 28 | * Paton, K. An algorithm for finding a fundamental set of cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. [https://dl.acm.org/citation.cfm?id=363232] 29 | """ 30 | function cycle_basis(g::AbstractSimpleGraph, root=nothing) 31 | T = eltype(g) 32 | cycles = Vector{Vector{T}}() 33 | 34 | nv(g) == 0 && return cycles 35 | 36 | gnodes = Set(vertices(g)) 37 | r::T = (root == nothing) ? pop!(gnodes) : T(root) 38 | while true 39 | stack = [r] 40 | pred = Dict(r => r) 41 | keys_pred = Set(r) 42 | used = Dict(r => T[]) 43 | keys_used = Set(r) 44 | while !isempty(stack) 45 | z = pop!(stack) 46 | zused = used[z] 47 | for nbr in neighbors(g,z) 48 | if !in(nbr, keys_used) 49 | pred[nbr] = z 50 | push!(keys_pred, nbr) 51 | push!(stack,nbr) 52 | used[nbr] = [z] 53 | push!(keys_used, nbr) 54 | elseif nbr == z 55 | push!(cycles, [z]) 56 | elseif !in(nbr, zused) 57 | pn = used[nbr] 58 | cycle = [nbr,z] 59 | p = pred[z] 60 | while !in(p, pn) 61 | push!(cycle, p) 62 | p = pred[p] 63 | end 64 | push!(cycle,p) 65 | push!(cycles,cycle) 66 | push!(used[nbr], z) 67 | end 68 | end 69 | end 70 | setdiff!(gnodes,keys_pred) 71 | isempty(gnodes) && break 72 | r = pop!(gnodes) 73 | end 74 | return cycles 75 | end 76 | -------------------------------------------------------------------------------- /src/biconnectivity/articulation.jl: -------------------------------------------------------------------------------- 1 | """ 2 | articulation(g) 3 | 4 | Compute the [articulation points](https://en.wikipedia.org/wiki/Biconnected_component) 5 | of a connected graph `g` and return an array containing all cut vertices. 6 | 7 | # Examples 8 | ```jldoctest 9 | julia> using LightGraphs 10 | 11 | julia> articulation(star_graph(5)) 12 | 1-element Array{Int64,1}: 13 | 1 14 | 15 | julia> articulation(path_graph(5)) 16 | 3-element Array{Int64,1}: 17 | 2 18 | 3 19 | 4 20 | ``` 21 | """ 22 | function articulation end 23 | @traitfn function articulation(g::AG::(!IsDirected)) where {T, AG<:AbstractGraph{T}} 24 | s = Vector{Tuple{T, T, T}}() 25 | is_articulation_pt = falses(nv(g)) 26 | low = zeros(T, nv(g)) 27 | pre = zeros(T, nv(g)) 28 | 29 | @inbounds for u in vertices(g) 30 | pre[u] != 0 && continue 31 | v = u 32 | children = 0 33 | wi::T = zero(T) 34 | w::T = zero(T) 35 | cnt::T = one(T) 36 | first_time = true 37 | 38 | while !isempty(s) || first_time 39 | first_time = false 40 | if wi < 1 41 | pre[v] = cnt 42 | cnt += 1 43 | low[v] = pre[v] 44 | v_neighbors = outneighbors(g, v) 45 | wi = 1 46 | else 47 | wi, u, v = pop!(s) 48 | v_neighbors = outneighbors(g, v) 49 | w = v_neighbors[wi] 50 | low[v] = min(low[v], low[w]) 51 | if low[w] >= pre[v] && u != v 52 | is_articulation_pt[v] = true 53 | end 54 | wi += 1 55 | end 56 | while wi <= length(v_neighbors) 57 | w = v_neighbors[wi] 58 | if pre[w] == 0 59 | if u == v 60 | children += 1 61 | end 62 | push!(s, (wi, u, v)) 63 | wi = 0 64 | u = v 65 | v = w 66 | break 67 | elseif w != u 68 | low[v] = min(low[v], pre[w]) 69 | end 70 | wi += 1 71 | end 72 | wi < 1 && continue 73 | end 74 | 75 | if children > 1 76 | is_articulation_pt[u] = true 77 | end 78 | end 79 | 80 | articulation_points = Vector{T}() 81 | 82 | for u in findall(is_articulation_pt) 83 | push!(articulation_points, T(u)) 84 | end 85 | 86 | return articulation_points 87 | end 88 | -------------------------------------------------------------------------------- /src/centrality/stress.jl: -------------------------------------------------------------------------------- 1 | """ 2 | stress_centrality(g[, vs]) 3 | stress_centrality(g, k) 4 | 5 | Calculate the [stress centrality](http://med.bioinf.mpi-inf.mpg.de/netanalyzer/help/2.7/#stressDist) 6 | of a graph `g` across all vertices, a specified subset of vertices `vs`, or a random subset of `k` 7 | vertices. Return a vector representing the centrality calculated for each node in `g`. 8 | 9 | The stress centrality of a vertex ``n`` is defined as the number of shortest paths passing through ``n``. 10 | 11 | ### References 12 | - Barabási, A.L., Oltvai, Z.N.: Network biology: understanding the cell's functional organization. Nat Rev Genet 5 (2004) 101-113 13 | - Shimbel, A.: Structural parameters of communication networks. Bull Math Biophys 15 (1953) 501-507. 14 | 15 | # Examples 16 | ```jldoctest 17 | julia> using LightGraphs 18 | 19 | julia> stress_centrality(star_graph(3)) 20 | 3-element Array{Int64,1}: 21 | 2 22 | 0 23 | 0 24 | 25 | julia> stress_centrality(cycle_graph(4)) 26 | 4-element Array{Int64,1}: 27 | 2 28 | 2 29 | 2 30 | 2 31 | ``` 32 | """ 33 | function stress_centrality(g::AbstractGraph, vs::AbstractVector=vertices(g)) 34 | n_v = nv(g) 35 | k = length(vs) 36 | isdir = is_directed(g) 37 | 38 | stress = zeros(Int, n_v) 39 | for s in vs 40 | if degree(g, s) > 0 41 | state = dijkstra_shortest_paths(g, s; allpaths=true, trackvertices=true) 42 | _stress_accumulate_basic!(stress, state, g, s) 43 | end 44 | end 45 | return stress 46 | end 47 | 48 | stress_centrality(g::AbstractGraph, k::Integer) = 49 | stress_centrality(g, sample(vertices(g), k)) 50 | 51 | function _stress_accumulate_basic!(stress::Vector{<:Integer}, 52 | state::DijkstraState, 53 | g::AbstractGraph, 54 | si::Integer) 55 | 56 | n_v = length(state.parents) # this is the ttl number of vertices 57 | δ = zeros(Int, n_v) 58 | P = state.predecessors 59 | 60 | laststress = copy(stress) 61 | # make sure the source index has no parents. 62 | P[si] = [] 63 | # we need to order the source vertices by decreasing distance for this to work. 64 | S = reverse(state.closest_vertices) #Replaced sortperm with this 65 | for w in S # w is the farthest vertex from si 66 | for v in P[w] # get the predecessors of w 67 | if v > 0 68 | δ[v] += δ[w] + 1 # increment sp of pred 69 | end 70 | end 71 | δ[w] *= length(P[w]) # adjust the # of sps of vertex 72 | if w != si 73 | stress[w] += δ[w] 74 | end 75 | end 76 | return nothing 77 | end 78 | 79 | -------------------------------------------------------------------------------- /src/Experimental/ShortestPaths/bellman-ford.jl: -------------------------------------------------------------------------------- 1 | # Parts of this code were taken / derived from Graphs.jl. See LICENSE for 2 | # licensing details. 3 | 4 | # The Bellman Ford algorithm for single-source shortest path 5 | 6 | struct NegativeCycleError <: Exception end 7 | 8 | """ 9 | struct BellmanFord <: ShortestPathAlgorithm 10 | 11 | The structure used to configure and specify that [`shortest_paths`](@ref) 12 | should use the [Bellman-Ford algorithm](http://en.wikipedia.org/wiki/Bellman–Ford_algorithm). 13 | No fields are specified or required. 14 | 15 | ### Implementation Notes 16 | `BellmanFord` supports the following shortest-path functionality: 17 | - negative distance matrices / weights 18 | - (optional) multiple sources 19 | - all destinations 20 | """ 21 | struct BellmanFord <: ShortestPathAlgorithm end 22 | struct BellmanFordResult{T, U<:Integer} <: ShortestPathResult 23 | parents::Vector{U} 24 | dists::Vector{T} 25 | end 26 | 27 | function shortest_paths( 28 | graph::AbstractGraph{U}, 29 | sources::AbstractVector{<:Integer}, 30 | distmx::AbstractMatrix{T}, 31 | ::BellmanFord 32 | ) where {T, U<:Integer} 33 | 34 | nvg = nv(graph) 35 | active = falses(nvg) 36 | active[sources] .= true 37 | dists = fill(typemax(T), nvg) 38 | parents = zeros(U, nvg) 39 | dists[sources] .= 0 40 | no_changes = false 41 | new_active = falses(nvg) 42 | 43 | @inbounds for i in vertices(graph) 44 | no_changes = true 45 | new_active .= false 46 | for u in vertices(graph)[active] 47 | for v in outneighbors(graph, u) 48 | relax_dist = distmx[u, v] + dists[u] 49 | if dists[v] > relax_dist 50 | dists[v] = relax_dist 51 | parents[v] = u 52 | no_changes = false 53 | new_active[v] = true 54 | end 55 | end 56 | end 57 | no_changes && break 58 | active, new_active = new_active, active 59 | end 60 | no_changes || throw(NegativeCycleError()) 61 | return BellmanFordResult(parents, dists) 62 | end 63 | 64 | shortest_paths(g::AbstractGraph, v::Integer, distmx::AbstractMatrix, alg::BellmanFord) = 65 | shortest_paths(g, [v], distmx, alg) 66 | 67 | has_negative_weight_cycle(g::AbstractGraph, ::BellmanFord) = false 68 | 69 | function has_negative_weight_cycle(g::AbstractGraph, distmx::AbstractMatrix, alg::BellmanFord) 70 | try 71 | shortest_paths(g, vertices(g), distmx, alg) 72 | catch e 73 | isa(e, ShortestPaths.NegativeCycleError) && return true 74 | end 75 | return false 76 | end 77 | -------------------------------------------------------------------------------- /test/utils.jl: -------------------------------------------------------------------------------- 1 | @testset "Utils" begin 2 | s = @inferred(LightGraphs.sample!([1:10;], 3)) 3 | @test length(s) == 3 4 | for e in s 5 | @test 1 <= e <= 10 6 | end 7 | 8 | s = @inferred(LightGraphs.sample!([1:10;], 6, exclude=[1, 2])) 9 | @test length(s) == 6 10 | for e in s 11 | @test 3 <= e <= 10 12 | end 13 | 14 | s = @inferred(LightGraphs.sample(1:10, 6, exclude=[1, 2])) 15 | @test length(s) == 6 16 | for e in s 17 | @test 3 <= e <= 10 18 | end 19 | 20 | # tests if isbounded has the correct behaviour 21 | bounded_int_types = [Int8, Int16, Int32, Int64, Int128, 22 | UInt8, UInt16, UInt32, UInt64, UInt128, 23 | Int, Bool] 24 | unbounded_int_types = [BigInt, Signed, Unsigned, Integer, Union{Int8, UInt8}] 25 | for T in bounded_int_types 26 | @test LightGraphs.isbounded(T) == true 27 | @test LightGraphs.isbounded(T(0)) == true 28 | end 29 | for T in unbounded_int_types 30 | @test LightGraphs.isbounded(T) == false 31 | if isconcretetype(T) 32 | @test LightGraphs.isbounded(T(0)) == false 33 | end 34 | end 35 | 36 | A = [false, true, false, false, true, true] 37 | @test findall(A) == LightGraphs.findall!(A, Vector{Int16}(undef, 6))[1:3] 38 | end 39 | 40 | 41 | 42 | 43 | @testset "Unweighted Contiguous Partition" begin 44 | 45 | p = @inferred(LightGraphs.unweighted_contiguous_partition(4, 2)) 46 | @test p == [1:2, 3:4] 47 | 48 | p = @inferred(LightGraphs.unweighted_contiguous_partition(10, 3)) 49 | @test p == [1:3, 4:6, 7:10] 50 | 51 | p = @inferred(LightGraphs.unweighted_contiguous_partition(4, 4)) 52 | @test p == [1:1, 2:2, 3:3, 4:4] 53 | end 54 | 55 | @testset "Greedy Contiguous Partition" begin 56 | 57 | p = @inferred(LightGraphs.greedy_contiguous_partition([1, 1, 1, 3], 2)) 58 | @test p == [1:3, 4:4] 59 | 60 | p = @inferred(LightGraphs.greedy_contiguous_partition([1, 2, 3, 4, 5, 100, 1, 3, 1, 1], 3)) 61 | @test p == [1:5, 6:6, 7:10] 62 | 63 | p = @inferred(LightGraphs.greedy_contiguous_partition([1, 1, 1, 1], 4)) 64 | @test p == [1:1, 2:2, 3:3, 4:4] 65 | end 66 | 67 | @testset "Optimal Contiguous Partition" begin 68 | 69 | p = @inferred(LightGraphs.optimal_contiguous_partition([1, 1, 1, 3], 2)) 70 | @test p == [1:3, 4:4] 71 | 72 | p = @inferred(LightGraphs.optimal_contiguous_partition([1, 2, 3, 4, 5, 100, 1, 3, 1, 1], 3)) 73 | @test p == [1:5, 6:6, 7:10] 74 | 75 | p = @inferred(LightGraphs.optimal_contiguous_partition([1, 1, 1, 1], 4)) 76 | @test p == [1:1, 2:2, 3:3, 4:4] 77 | end 78 | -------------------------------------------------------------------------------- /docs/src/parallel.md: -------------------------------------------------------------------------------- 1 | # Parallel Graph Algorithms 2 | 3 | LightGraphs.Parallel is a module for graph algorithms that are parallelized. Their names should be consistent with the 4 | serial versions in the main module. In order to use parallel versions of the algorithms you can write: 5 | 6 | ```julia 7 | using LightGraphs 8 | import LightGraphs.Parallel 9 | 10 | g = path_graph(10) 11 | bc = Parallel.betweenness_centrality(g) 12 | ``` 13 | 14 | The arguments to parallel versions of functions match as closely as possible their serial versions 15 | with potential addition default or keyword arguments to control parallel execution. 16 | One exception is that for algorithms that cannot be meaningfully parallelized for 17 | certain types of arguments a MethodError will be raised. 18 | For example, `dijkstra_shortest_paths` works for either a single or multiple source argument, 19 | but since the parallel version is slower when given only a single source, it will raise a `MethodError`. 20 | 21 | ```julia 22 | g = Graph(10) 23 | # these work 24 | LightGraphs.dijkstra_shortest_paths(g,1) 25 | LightGraphs.dijkstra_shortest_paths(g, [1,2]) 26 | Parallel.dijkstra_shortest_paths(g, [1,2]) 27 | # this doesn't 28 | Parallel.dijkstra_shortest_paths(g,1) 29 | ``` 30 | 31 | Note that after `import`ing or `using` `LightGraphs.Parallel`, you must fully qualify the version of the function you wish to use (using, _e.g._, `LightGraphs.betweenness_centrality(g)` for the sequential version and 32 | `Parallel.betweenness_centrality(g)` for the parallel version.) 33 | 34 | The following is a current list of parallel algorithms: 35 | - Centrality measures: 36 | - `Parallel.betweenness_centrality` 37 | - `Parallel.closeness_centrality` 38 | - `Parallel.pagerank` 39 | - `Parallel.radiality_centrality` 40 | - `Parallel.stress_centrality` 41 | 42 | 43 | - Distance measures: 44 | - `Parallel.center` 45 | - `Parallel.diameter` 46 | - `Parallel.eccentricity` 47 | - `Parallel.radius` 48 | 49 | - Shortest paths algorithms: 50 | - `Parallel.bellman_ford_shortest_paths` 51 | - `Parallel.dijkstra_shortest_paths` 52 | - `Parallel.floyd_warshall_shortest_paths` 53 | - `Paralell.johnson_shortest_paths` 54 | 55 | - Traversal algorithms: 56 | - `Parallel.bfs` 57 | - `Parallel.greedy_color` 58 | 59 | Also note that in some cases, the arguments for the parallel versions may differ from the serial (standard) versions. As an example, parallel Dijkstra shortest paths takes advantage of multiple processors to execute centrality from multiple source vertices. It is an error to pass a single source vertex into the parallel version of dijkstra_shortest_paths. 60 | 61 | -------------------------------------------------------------------------------- /src/Experimental/ShortestPaths/bfs.jl: -------------------------------------------------------------------------------- 1 | import Base.Sort, Base.Sort.Algorithm 2 | import Base:sort! 3 | struct NOOPSortAlg <: Base.Sort.Algorithm end 4 | const NOOPSort = NOOPSortAlg() 5 | 6 | sort!(x, ::Integer, ::Integer, ::ShortestPaths.NOOPSortAlg, ::Base.Sort.Ordering) = x 7 | 8 | """ 9 | struct BFS <: ShortestPathAlgorithm 10 | 11 | The structure used to configure and specify that [`shortest_paths`](@ref) 12 | should use the [Breadth-First Search algorithm](https://en.m.wikipedia.org/wiki/Breadth-first_search). 13 | 14 | An optional sorting algorithm may be specified (default = no sorting). 15 | Sorting helps maintain cache locality and will improve performance on 16 | very large graphs; for normal use, sorting will incur a performance 17 | penalty. 18 | 19 | `BFS` is the default algorithm used when a source is specified 20 | but no distance matrix is specified. 21 | 22 | ### Implementation Notes 23 | `BFS` supports the following shortest-path functionality: 24 | - (optional) multiple sources 25 | - all destinations 26 | """ 27 | struct BFS{T<:Base.Sort.Algorithm} <: ShortestPathAlgorithm 28 | sort_alg::T 29 | end 30 | 31 | BFS() = BFS(NOOPSort) 32 | 33 | struct BFSResult{U<:Integer} <: ShortestPathResult 34 | parents::Vector{U} 35 | dists::Vector{U} 36 | end 37 | 38 | function shortest_paths( 39 | g::AbstractGraph{U}, 40 | ss::AbstractVector{U}, 41 | alg::BFS, 42 | ) where U<:Integer 43 | 44 | 45 | n = nv(g) 46 | dists = fill(typemax(U), n) 47 | parents = zeros(U, n) 48 | visited = falses(n) 49 | n_level = one(U) 50 | cur_level = Vector{U}() 51 | sizehint!(cur_level, n) 52 | next_level = Vector{U}() 53 | sizehint!(next_level, n) 54 | @inbounds for s in ss 55 | dists[s] = zero(U) 56 | visited[s] = true 57 | push!(cur_level, s) 58 | end 59 | while !isempty(cur_level) 60 | @inbounds for v in cur_level 61 | @inbounds @simd for i in outneighbors(g, v) 62 | if !visited[i] 63 | push!(next_level, i) 64 | dists[i] = n_level 65 | parents[i] = v 66 | visited[i] = true 67 | end 68 | end 69 | end 70 | n_level += one(U) 71 | empty!(cur_level) 72 | cur_level, next_level = next_level, cur_level 73 | sort!(cur_level, alg=alg.sort_alg) 74 | end 75 | return BFSResult(parents, dists) 76 | end 77 | 78 | shortest_paths(g::AbstractGraph{U}, ss::AbstractVector{<:Integer}, alg::BFS) where {U<:Integer} = shortest_paths(g, U.(ss), alg) 79 | shortest_paths(g::AbstractGraph{U}, s::Integer, alg::BFS) where {U<:Integer} = shortest_paths(g, Vector{U}([s]), alg) 80 | -------------------------------------------------------------------------------- /test/cycles/johnson.jl: -------------------------------------------------------------------------------- 1 | @testset "Cycles" begin 2 | completedg = complete_digraph(4) 3 | pathdg = path_digraph(5) 4 | triangle = random_regular_graph(3, 2) 5 | quadrangle = random_regular_graph(4, 2) 6 | pentagon = random_regular_graph(5, 2) 7 | 8 | @testset "path digraph" for g in testgraphs(pathdg) 9 | @test maxsimplecycles(g) == 0 10 | @test maxsimplecycles(g, false) == 84 11 | @test simplecyclescount(g) == 0 12 | @test length(simplecycles(g)) == 0 13 | @test isempty(simplecycles(g)) == true 14 | @test isempty(simplecycles_iter(g)) == true 15 | @test simplecycleslength(g) == (zeros(5), 0) 16 | @test simplecyclescount(g, 10) == 0 17 | @test isempty(simplecycles_iter(g, 10)) == true 18 | @test simplecycleslength(g, 10) == (zeros(5), 0) 19 | end 20 | 21 | @testset "maxsimplecycles(4)" begin 22 | @test maxsimplecycles(4) == 20 23 | end 24 | 25 | @testset "complete digraph" for g in testgraphs(completedg) 26 | @test maxsimplecycles(g) == 20 27 | @test length(simplecycles(g)) == 20 28 | @test simplecycles(g) == @inferred(simplecycles_iter(g)) 29 | @test simplecyclescount(g) == 20 30 | @test simplecycleslength(g) == ([0, 6, 8, 6], 20) 31 | @test simplecyclescount(g, 10) == 10 32 | @test simplecycleslength(g, 10)[2] == 10 33 | end 34 | 35 | @testset "triangle" for g in testgraphs(triangle) 36 | trianglelengths, triangletotal = simplecycleslength(DiGraph(g)) 37 | @test sum(trianglelengths) == triangletotal 38 | end 39 | 40 | @testset "quadrangle" for g in testgraphs(quadrangle) 41 | quadranglelengths, quadrangletotal = simplecycleslength(DiGraph(g)) 42 | @test sum(quadranglelengths) == quadrangletotal 43 | @test simplecycles(DiGraph(g)) == @inferred(simplecycles_iter(DiGraph(g))) 44 | end 45 | 46 | @testset "pentagon" for g in testgraphs(pentagon) 47 | pentagonlengths, pentagontotal = simplecycleslength(DiGraph(g)) 48 | @test sum(pentagonlengths) == pentagontotal 49 | end 50 | 51 | selfloopg = DiGraph([ 52 | 0 1 0 0; 53 | 0 0 1 0; 54 | 1 0 1 0; 55 | 0 0 0 1; 56 | ]) 57 | 58 | @testset "self loops" for g in testgraphs(selfloopg) 59 | cycles = simplecycles(g) 60 | @test [3] in cycles 61 | @test [4] in cycles 62 | @test [1, 2, 3] in cycles || [2, 3, 1] in cycles || [3, 1, 2] in cycles 63 | @test length(cycles) == 3 64 | 65 | cycles2 = simplecycles_iter(g) 66 | @test [3] in cycles2 67 | @test [4] in cycles2 68 | @test [1, 2, 3] in cycles2 || [2, 3, 1] in cycles2 || [3, 1, 2] in cycles2 69 | @test length(cycles2) == 3 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /src/cycles/hawick-james.jl: -------------------------------------------------------------------------------- 1 | """ 2 | simplecycles_hawick_james(g) 3 | 4 | Find circuits (including self-loops) in `g` using the algorithm 5 | of Hawick & James. 6 | 7 | ### References 8 | - Hawick & James, "Enumerating Circuits and Loops in Graphs with Self-Arcs and Multiple-Arcs", 2008 9 | """ 10 | function simplecycles_hawick_james end 11 | # see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax 12 | @traitfn function simplecycles_hawick_james(g::AG::IsDirected) where {T, AG<:AbstractGraph{T}} 13 | nvg = nv(g) 14 | B = Vector{T}[Vector{T}() for i in vertices(g)] 15 | blocked = zeros(Bool, nvg) 16 | stack = Vector{T}() 17 | cycles = Vector{Vector{T}}() 18 | for v in vertices(g) 19 | circuit_recursive!(g, v, v, blocked, B, stack, cycles) 20 | resetblocked!(blocked) 21 | resetB!(B) 22 | end 23 | return cycles 24 | end 25 | 26 | """ 27 | resetB!(B) 28 | 29 | Reset B work structure. 30 | """ 31 | resetB!(B) = map!(empty!, B, B) 32 | 33 | """ 34 | resetblocked!(blocked) 35 | 36 | Reset vector of `blocked` vertices. 37 | """ 38 | resetblocked!(blocked) = fill!(blocked, false) 39 | 40 | """ 41 | circuit_recursive!(g, v1, v2, blocked, B, stack, cycles) 42 | 43 | Find circuits in `g` recursively starting from v1. 44 | """ 45 | function circuit_recursive! end 46 | @traitfn function circuit_recursive!(g::::IsDirected, v1::T, v2::T, blocked::AbstractVector, B::Vector{Vector{T}}, stack::Vector{T}, cycles::Vector{Vector{T}}) where T<:Integer 47 | f = false 48 | push!(stack, v2) 49 | blocked[v2] = true 50 | 51 | Av = outneighbors(g, v2) 52 | for w in Av 53 | (w < v1) && continue 54 | if w == v1 # Found a circuit 55 | push!(cycles, copy(stack)) 56 | f = true 57 | elseif !blocked[w] 58 | f |= circuit_recursive!(g, v1, w, blocked, B, stack, cycles) 59 | end 60 | end 61 | if f 62 | unblock!(v2, blocked, B) 63 | else 64 | for w in Av 65 | (w < v1) && continue 66 | if !(v2 in B[w]) 67 | push!(B[w], v2) 68 | end 69 | end 70 | end 71 | pop!(stack) 72 | return f 73 | end 74 | 75 | """ 76 | unblock!(v, blocked, B) 77 | 78 | Unblock the value `v` from the `blocked` list and remove from `B`. 79 | """ 80 | function unblock!(v::T, blocked::AbstractVector, B::Vector{Vector{T}}) where T 81 | blocked[v] = false 82 | wPos = 1 83 | Bv = B[v] 84 | while wPos <= length(Bv) 85 | w = Bv[wPos] 86 | old_length = length(Bv) 87 | filter!(v -> v != w, Bv) 88 | wPos += 1 - (old_length - length(Bv)) 89 | if blocked[w] 90 | unblock!(w, blocked, B) 91 | end 92 | end 93 | return nothing 94 | end 95 | --------------------------------------------------------------------------------