diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index 845d2b5dc..09677119c 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -23,5 +23,4 @@ jobs: # remove this after 2.0 - name: test package run: julia --color=yes --project -e 'using Pkg; Pkg.test(coverage=true, julia_args=["--depwarn=no"])' - # uncomment after 2.0 - # - uses: julia-actions/julia-runtest@latest + continue-on-error: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f379e68ad..af2fcc514 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,5 @@ jobs: # uncomment after 2.0 # - uses: julia-actions/julia-runtest@latest - uses: julia-actions/julia-uploadcodecov@latest - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} if: ${{ matrix.julia-version == '1' && matrix.os =='ubuntu-latest' }} diff --git a/.github/workflows/documenter.yml b/.github/workflows/documenter.yml index c20535b2a..43fe90fb8 100644 --- a/.github/workflows/documenter.yml +++ b/.github/workflows/documenter.yml @@ -1,20 +1,21 @@ -name: Documenter +name: Documentation + on: push: - branches: [stable] - tags: [v*] + branches: + - master + tags: '*' pull_request: jobs: - docs: - name: Documentation + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest - with: - version: 1.4 - - uses: julia-actions/julia-docdeploy@releases/v1 + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token + run: julia --project=docs/ docs/make.jl diff --git a/README.md b/README.md index cdb63f269..72322e3e5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # LightGraphs -[![Build Status](https://travis-ci.org/JuliaGraphs/LightGraphs.jl.svg?branch=master)](https://travis-ci.org/JuliaGraphs/LightGraphs.jl) +[![Build Status](https://github.com/JuliaGraphs/LightGraphs.jl/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/JuliaGraphs/LightGraphs.jl/actions/workflows/ci.yml?query=branch%3Amaster) [![codecov.io](http://codecov.io/github/JuliaGraphs/LightGraphs.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaGraphs/LightGraphs.jl?branch=master) [![](https://img.shields.io/badge/docs-latest-blue.svg)](https://juliagraphs.github.io/LightGraphs.jl/latest) -[![Join the chat at https://gitter.im/JuliaGraphs/LightGraphs.jl](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/JuliaGraphs/LightGraphs.jl) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.889971.svg)](https://doi.org/10.5281/zenodo.889971) +**Project Status:** As of 8 October 2021 LightGraphs is no longer under active development. It will remain available on Github at [sbromberger/LightGraphs.jl](https://github.com/sbromberger/LightGraphs.jl). The JuliaGraphs organization will continue to maintain packages that use LightGraphs and transition development over the long term. + LightGraphs offers both (a) a set of simple, concrete graph implementations -- `Graph` (for undirected graphs) and `DiGraph` (for directed graphs), and (b) an API for the development of more sophisticated graph implementations under the `AbstractGraph` diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index e3797875a..6b9d8c4c5 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -1,36 +1,22 @@ -using PkgBenchmark +using BenchmarkTools using LightGraphs -testdatadir = joinpath(dirname(@__FILE__), "..", "test", "testdata") -benchdatadir = joinpath(dirname(@__FILE__), "data") -paramsfile = joinpath(benchdatadir, "params.jld") - -println("testdatadir = $testdatadir") -println("paramsfile = $paramsfile") - -dg1fn = joinpath(testdatadir, "graph-5k-50k.jgz") - DIGRAPHS = Dict{String,DiGraph}( "complete100" => complete_digraph(100), - "5000-50000" => LightGraphs.load(dg1fn)["graph-5000-50000"], "path500" => path_digraph(500) ) GRAPHS = Dict{String,Graph}( "complete100" => complete_graph(100), "tutte" => smallgraph(:tutte), - "path500" => path_graph(500), - "5000-49947" => SimpleGraph(DIGRAPHS["5000-50000"]) + "path500" => path_graph(500) ) -@benchgroup "LightGraphsBenchmarks" begin - include("core.jl") - include("parallel/egonets.jl") - include("insertions.jl") - include("edges.jl") - include("centrality.jl") - include("connectivity.jl") - include("traversals.jl") -end +suite = BenchmarkGroup() +include("core.jl") + + +tune!(suite); +results = run(suite, verbose = true, seconds = 10) diff --git a/benchmark/core.jl b/benchmark/core.jl index 5446dbf72..10cfceb59 100644 --- a/benchmark/core.jl +++ b/benchmark/core.jl @@ -1,4 +1,13 @@ -function bench_iteredges(g::AbstractGraph) +suite["core"] = BenchmarkGroup(["nv", "edges", "has_edge"]) + + +# nv +suite["core"]["nv"] = BenchmarkGroup(["graphs", "digraphs"]) +suite["core"]["nv"]["graphs"] = @benchmarkable [nv(g) for (n,g) in $GRAPHS] +suite["core"]["nv"]["digraphs"] = @benchmarkable [nv(g) for (n,g) in $DIGRAPHS] + +# iterate edges +function iter_edges(g::AbstractGraph) i = 0 for e in edges(g) i += 1 @@ -6,8 +15,12 @@ function bench_iteredges(g::AbstractGraph) return i end -function bench_has_edge(g::AbstractGraph) - seed!(1) +suite["core"]["edges"] = BenchmarkGroup(["graphs", "digraphs"]) +suite["core"]["edges"]["graphs"] = @benchmarkable [iter_edges(g) for (n,g) in $GRAPHS] +suite["core"]["edges"]["digraphs"] = @benchmarkable [iter_edges(g) for (n,g) in $DIGRAPHS] + +# has edge +function all_has_edge(g::AbstractGraph) nvg = nv(g) srcs = rand([1:nvg;], cld(nvg, 4)) dsts = rand([1:nvg;], cld(nvg, 4)) @@ -20,26 +33,6 @@ function bench_has_edge(g::AbstractGraph) return i end - -EDGEFNS = [ - bench_iteredges, - bench_has_edge -] - -@benchgroup "edges" begin - - for fun in EDGEFNS - @benchgroup "$fun" begin - @benchgroup "graph" begin - for (name, g) in GRAPHS - @bench "$name" $fun($g) - end - end - @benchgroup "digraph" begin - for (name, g) in DIGRAPHS - @bench "$name" $fun($g) - end - end # digraph - end # fun - end -end # edges +suite["core"]["has_edge"] = BenchmarkGroup(["graphs", "digraphs"]) +suite["core"]["has_edge"]["graphs"] = @benchmark [all_has_edge(g) for (n,g) in $GRAPHS] +suite["core"]["has_edge"]["digraphs"] = @benchmark [all_has_edge(g) for (n,g) in $DIGRAPHS] diff --git a/docs/Project.toml b/docs/Project.toml index 627801d40..48e2de9da 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -2,4 +2,4 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" [compat] -Documenter = "~0.23.4" +Documenter = "~0.26.2" diff --git a/docs/make.jl b/docs/make.jl index 8e1fa35a4..7e8deaaec 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -19,6 +19,7 @@ makedocs( "Making and Modifying Graphs" => "generators.md", "Reading / Writing Graphs" => "persistence.md", "Operators" => "operators.md", + "Core Functions" => "core.md", "Plotting Graphs" => "plotting.md", "Path and Traversal" => "pathing.md", "Coloring" => "coloring.md", diff --git a/docs/src/community.md b/docs/src/community.md index beafa88fb..2654978ef 100644 --- a/docs/src/community.md +++ b/docs/src/community.md @@ -17,7 +17,8 @@ Pages = [ "community/clustering.jl", "community/core-periphery.jl", "community/label_propagation.jl", - "community/modularity.jl" + "community/modularity.jl", + "community/assortativity.jl" ] Private = false ``` diff --git a/docs/src/developing.md b/docs/src/developing.md index f02c76193..60a28c523 100644 --- a/docs/src/developing.md +++ b/docs/src/developing.md @@ -17,7 +17,7 @@ within the LightGraphs package should just work: - [`outneighbors`](@ref) - [`vertices`](@ref) - [`is_directed`](@ref): Note that since LightGraphs uses traits to determine directedness, `is_directed` for a `CustomGraph` type -should be implemented with **both** of the following signatures: + should be implemented with **both** of the following signatures: - `is_directed(::Type{CustomGraph})::Bool` (example: `is_directed(::Type{<:CustomGraph}) = false`) - `is_directed(g::CustomGraph)::Bool` - [`zero`](@ref) diff --git a/docs/src/graphtypes.md b/docs/src/graphtypes.md index 2e3b0b9b0..e1d9bd540 100644 --- a/docs/src/graphtypes.md +++ b/docs/src/graphtypes.md @@ -13,4 +13,4 @@ These are general guidelines to help you select the proper graph type. - In general, prefer the native `SimpleGraphs`/`SimpleDiGraphs` structures in [LightGraphs.jl](https://github.com/JuliaGraphs/LightGraphs.jl). - If you need edge weights and don't require large numbers of graph modifications, use [SimpleWeightedGraphs](https://github.com/JuliaGraphs/SimpleWeightedGraphs.jl). - If you need labeling of vertices or edges, use [MetaGraphs](https://github.com/JuliaGraphs/MetaGraphs.jl). -- 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). +- If you work with very large graphs (billions to tens of billions of edges) and don't need mutability, use [StaticGraphs](https://github.com/JuliaGraphs/StaticGraphs.jl). diff --git a/docs/src/pathing.md b/docs/src/pathing.md index bde4c1cbf..5c0d2734c 100644 --- a/docs/src/pathing.md +++ b/docs/src/pathing.md @@ -32,6 +32,7 @@ Any graph traversal will traverse an edge only if it is present in the graph. W ```@docs bfs_tree +topological_sort_by_dfs dfs_tree maximum_adjacency_visit bfs_parents diff --git a/docs/src/plotting.md b/docs/src/plotting.md index 7ce7248ee..b97ec9eb9 100644 --- a/docs/src/plotting.md +++ b/docs/src/plotting.md @@ -66,3 +66,29 @@ end The above code produces the following output: ![alt tag](https://raw.githubusercontent.com/abhijithanilkumar/NetworkViz.jl/master/examples/networkviz.gif) + + +## [SGtSNEpi.jl](https://github.com/fcdimitr/SGtSNEpi.jl) +SGtSNEpi.jl is a high-performance software for swift embedding of a large, sparse graph into a d-dimensional space (d = 1,2,3). The [Makie](http://makie.juliaplots.org) plotting ecosystem is used for interactive plots. + +```julia +using GLMakie, SGtSNEpi, SNAPDatasets + +GLMakie.activate!() + +g = loadsnap(:as_caida) +y = sgtsnepi(g); +show_embedding(y; + A = adjacency_matrix(g), # show edges on embedding + mrk_size = 1, # control node sizes + lwd_in = 0.01, lwd_out = 0.001, # control edge widths + edge_alpha = 0.03 ) # control edge transparency +``` + +The above code produces the following output: + +![alt tag](https://github.com/fcdimitr/SGtSNEpi.jl/raw/master/docs/src/assets/as_caida.png) + +SGtSNEpi.jl enables 3D graph embedding as well. The 3D embedding of the weighted undirected graph [ML\_Graph/optdigits\_10NN](https://sparse.tamu.edu/ML_Graph/optdigits_10NN) is shown below. It consists of 26,475 nodes and 53,381 edges. Nodes are colored according to labels provided with the dataset. + +![alt tag](https://fcdimitr.github.io/SGtSNEpi.jl/v0.1.0/sgtsnepi-animation.gif) diff --git a/src/LightGraphs.jl b/src/LightGraphs.jl index c01e39066..221092db4 100644 --- a/src/LightGraphs.jl +++ b/src/LightGraphs.jl @@ -114,7 +114,7 @@ barabasi_albert!, static_fitness_model, static_scale_free, kronecker, dorogovtse #community modularity, core_periphery_deg, local_clustering,local_clustering_coefficient, global_clustering_coefficient, triangles, -label_propagation, maximal_cliques, clique_percolation, +label_propagation, maximal_cliques, clique_percolation, assortativity, #generators complete_graph, star_graph, path_graph, wheel_graph, cycle_graph, @@ -255,6 +255,7 @@ include("community/core-periphery.jl") include("community/clustering.jl") include("community/cliques.jl") include("community/clique_percolation.jl") +include("community/assortativity.jl") include("spanningtrees/boruvka.jl") include("spanningtrees/kruskal.jl") include("spanningtrees/prim.jl") diff --git a/src/SimpleGraphs/generators/randgraphs.jl b/src/SimpleGraphs/generators/randgraphs.jl index 26c4b7360..cd6fc9036 100644 --- a/src/SimpleGraphs/generators/randgraphs.jl +++ b/src/SimpleGraphs/generators/randgraphs.jl @@ -223,8 +223,24 @@ end watts_strogatz(n, k, β) Return a [Watts-Strogatz](https://en.wikipedia.org/wiki/Watts_and_Strogatz_model) -small model random graph with `n` vertices, each with degree `k`. Edges are -randomized per the model based on probability `β`. +small world random graph with `n` vertices, each with expected degree `k` +(or `k - 1` if `k` is odd). Edges are randomized per the model based on probability `β`. + +The algorithm proceeds as follows. First, a perfect 1-lattice is constructed, +where each vertex has exacly `div(k, 2)` neighbors on each side (i.e., `k` or +`k - 1` in total). Then the following steps are repeated for a hop length `i` of +`1` through `div(k, 2)`. + +1. Consider each vertex `s` in turn, along with the edge to its `i`th nearest + neighbor `t`, in a clockwise sense. + +2. Generate a uniformly random number `r`. If `r ≥ β`, then the edge `(s, t)` is + left unaltered. Otherwise, the edge is deleted and *rewired* so that `s` is + connected to some vertex `d`, chosen uniformly at random from the entire + graph, excluding `s` and its neighbors. (Note that `t` is a valid candidate.) + +For `β = 1`, the graph will remain a 1-lattice, and for `β = 0`, all edges will +be rewired randomly. ### Optional Arguments - `is_directed=false`: if true, return a directed graph. @@ -238,39 +254,65 @@ julia> watts_strogatz(10, 4, 0.3) julia> watts_strogatz(Int8(10), 4, 0.8, is_directed=true, seed=123) {10, 20} directed simple Int8 graph ``` + +### References +- Collective dynamics of ‘small-world’ networks, Duncan J. Watts, Steven H. Strogatz. [https://doi.org/10.1038/30918](https://doi.org/10.1038/30918) +- Small Worlds, Duncan J. watts. [https://en.wikipedia.org/wiki/Special:BookSources?isbn=978-0691005416](https://en.wikipedia.org/wiki/Special:BookSources?isbn=978-0691005416) """ function watts_strogatz(n::Integer, k::Integer, β::Real; is_directed=false, seed::Int=-1) @assert k < n - if is_directed - g = SimpleDiGraph(n) - else - g = SimpleGraph(n) + + # If we have n - 1 neighbors (exactly k/2 on each side), then the graph is + # necessarily complete. No need to run the Watts-Strogatz procedure: + if k == n - 1 && iseven(k) + return is_directed ? complete_digraph(n) : complete_graph(n) end + + g = is_directed ? SimpleDiGraph(n) : SimpleGraph(n) + rng = getRNG(seed) - for s in 1:n - for i in 1:(floor(Integer, k / 2)) - target = ((s + i - 1) % n) + 1 - if rand(rng) > β && !has_edge(g, s, target) # TODO: optimize this based on return of add_edge! - add_edge!(g, s, target) - else - while true - d = target - while d == target - d = rand(rng, 1:(n - 1)) - if s < d - d += 1 - end - end - if s != d - add_edge!(g, s, d) && break - end - end + + # The ith next vertex, in clockwise order. + # (Reduce to zero-based indexing, so the modulo works, by subtracting 1 + # before and adding 1 after.) + @inline target(s, i) = ((s + i - 1) % n) + 1 + + # Phase 1: For each step size i, add an edge from each vertex s to the ith + # next vertex, in clockwise order. + + for i = 1:div(k, 2), s = 1:n + add_edge!(g, s, target(s, i)) + end + + # Phase 2: For each step size i and each vertex s, consider the edge to the + # ith next vertex, in clockwise order. With probability β, delete the edge + # and rewire it to any (valid) target, chosen uniformly at random. + + for i = 1:div(k, 2), s = 1:n + + # We only rewire with a probability β, and we only worry about rewiring + # if there is some vertex not connected to s; otherwise, the only valid + # rewiring is to reconnect to the ith next vertex, and there is no work + # to do. + (rand(rng) < β && degree(g, s) < n - 1) || continue + + t = target(s, i) + + while true + d = rand(1:n) # Tentative new target + d == s && continue # Self-loops prohibited + d == t && break # Rewired to original target + if add_edge!(g, s, d) # Was this valid (i.e., unconnected)? + rem_edge!(g, s, t) # True rewiring: Delete original edge + break # We found a valid target end end + end return g end + function _suitable(edges::Set{SimpleEdge{T}}, potential_edges::Dict{T,T}) where T <: Integer isempty(potential_edges) && return true list = keys(potential_edges) diff --git a/src/community/assortativity.jl b/src/community/assortativity.jl new file mode 100644 index 000000000..0d17ae5f4 --- /dev/null +++ b/src/community/assortativity.jl @@ -0,0 +1,53 @@ +""" + assortativity(g) + +Return the [assortativity coefficient](https://en.wikipedia.org/wiki/Assortativity) +of graph `g`, defined as the Pearson correlation of excess degree between +the end vertices of all the edges of the graph. + +The excess degree is equal to the degree of linked vertices minus one, +i.e. discounting the edge that links the pair. +In directed graphs, the paired values are the out-degree of source vertices +and the in-degree of destination vertices. + +# Examples +```jldoctest +julia> using LightGraphs + +julia> assortativity(star_graph(4)) +-1.0 +``` +""" +function assortativity(g::AbstractGraph{T}) where T + P = promote_type(Int64, T) # at least Int64 to reduce risk of overflow + nue = ne(g) + sjk = sj = sk = sjs = sks = zero(P) + for d in edges(g) + j = P(outdegree(g, src(d)) - 1) + k = P(indegree(g, dst(d)) - 1) + sjk += j*k + sj += j + sk += k + sjs += j^2 + sks += k^2 + end + return assortativity_coefficient(g, sjk, sj, sk, sjs, sks, nue) +end + +#= +assortativity coefficient for directed graphs: +see equation (21) in M. E. J. Newman: Mixing patterns in networks, Phys. Rev. E 67, 026126 (2003), +http://arxiv.org/abs/cond-mat/0209450 +=# +@traitfn function assortativity_coefficient(g::::IsDirected, sjk, sj, sk, sjs, sks, nue) + return (sjk - sj*sk/nue) / sqrt((sjs - sj^2/nue)*(sks - sk^2/nue)) +end + +#= +assortativity coefficient for undirected graphs: +see equation (4) in M. E. J. Newman: Assortative mixing in networks, Phys. Rev. Lett. 89, 208701 (2002), +http://arxiv.org/abs/cond-mat/0205405/ +=# +@traitfn function assortativity_coefficient(g::::(!IsDirected), sjk, sj, sk, sjs, sks, nue) + return (sjk/nue - ((sj + sk)/(2*nue))^2) / ((sjs + sks)/(2*nue) - ((sj + sk)/(2*nue))^2) +end diff --git a/src/connectivity.jl b/src/connectivity.jl index 0c597b489..c927bed15 100644 --- a/src/connectivity.jl +++ b/src/connectivity.jl @@ -216,7 +216,6 @@ julia> strongly_connected_components(g) ``` """ - function strongly_connected_components end # see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax @traitfn function strongly_connected_components(g::AG::IsDirected) where {T<:Integer, AG <: AbstractGraph{T}} @@ -374,7 +373,6 @@ julia> strongly_connected_components_kosaraju(g) ``` """ - function strongly_connected_components_kosaraju end @traitfn function strongly_connected_components_kosaraju(g::AG::IsDirected) where {T<:Integer, AG <: AbstractGraph{T}} diff --git a/src/linalg/spectral.jl b/src/linalg/spectral.jl index 5f6534f80..80e12e075 100644 --- a/src/linalg/spectral.jl +++ b/src/linalg/spectral.jl @@ -153,34 +153,14 @@ between `v` and `i`. """ function incidence_matrix(g::AbstractGraph, T::DataType=Int; oriented=false) isdir = is_directed(g) - n_v = nv(g) n_e = ne(g) - nz = 2 * n_e - - # every col has the same 2 entries - colpt = collect(1:2:(nz + 1)) - nzval = repeat([(isdir || oriented) ? -one(T) : one(T), one(T)], n_e) - - # iterate over edges for row indices - rowval = zeros(Int, nz) - i = 1 - for u in vertices(g) - for v in outneighbors(g, u) - if isdir || u < v # add every edge only once - if u > v - v, u = u, v - # need to make sure that columns of the CSC matrix are sorted - nzval[2 * i - 1], nzval[2 * i] = nzval[2 * i], nzval[2 * i - 1] - end - rowval[2 * i - 1] = u - rowval[2 * i] = v - i += 1 - end - end - end - - spmx = SparseMatrixCSC(n_v, n_e, colpt, rowval, nzval) - return spmx + I = vcat(src.(edges(g)), dst.(edges(g))) + J = vcat(1:n_e, 1:n_e) + V = vcat( + (isdir || oriented) ? -fill(one(T), n_e) : fill(one(T), n_e), + fill(one(T), n_e), + ) + return sparse(I, J, V, nv(g), ne(g)) end """ diff --git a/src/persistence/lg.jl b/src/persistence/lg.jl index e87267778..8bc587e81 100644 --- a/src/persistence/lg.jl +++ b/src/persistence/lg.jl @@ -63,7 +63,7 @@ function _parse_header(s::AbstractString) if occursin(",", graphname) # version number and type graphname, _ver, _dtype, graphcode = split(graphname, r"s*,s*") ver = parse(Int, _ver) - dtype = eval(Symbol(_dtype)) + dtype = getfield(Main, Symbol(_dtype)) addl_info = true end n_v = parse(Int, nvstr) diff --git a/src/shortestpaths/bellman-ford.jl b/src/shortestpaths/bellman-ford.jl index 9b4d3e0cf..9dcce4a2c 100644 --- a/src/shortestpaths/bellman-ford.jl +++ b/src/shortestpaths/bellman-ford.jl @@ -110,7 +110,7 @@ will return a vector (indexed by destination vertex) of paths from source `v` to all other vertices. In addition, `enumerate_paths(state, v, d)` will return a vector representing the path from vertex `v` to vertex `d`. """ -function enumerate_paths(state::AbstractPathState, vs::Vector{<:Integer}) +function enumerate_paths(state::AbstractPathState, vs::AbstractVector{<:Integer}) parents = state.parents T = eltype(parents) @@ -131,6 +131,6 @@ function enumerate_paths(state::AbstractPathState, vs::Vector{<:Integer}) return all_paths end -enumerate_paths(state::AbstractPathState, v) = enumerate_paths(state, [v])[1] +enumerate_paths(state::AbstractPathState, v::Integer) = enumerate_paths(state, [v])[1] enumerate_paths(state::AbstractPathState) = enumerate_paths(state, [1:length(state.parents);]) diff --git a/src/shortestpaths/spfa.jl b/src/shortestpaths/spfa.jl index 8dec12856..f3f1859aa 100644 --- a/src/shortestpaths/spfa.jl +++ b/src/shortestpaths/spfa.jl @@ -41,7 +41,6 @@ julia> spfa_shortest_paths(gx, 1, d) ``` """ - function spfa_shortest_paths( graph::AbstractGraph{U}, source::Integer, diff --git a/test/community/assortativity.jl b/test/community/assortativity.jl new file mode 100644 index 000000000..720668945 --- /dev/null +++ b/test/community/assortativity.jl @@ -0,0 +1,23 @@ +using Random, Statistics + +@testset "Assortativity" begin + # Test definition of assortativity as Pearson correlation coefficient + # between excess of degrees + @testset "Small graphs" for n = 5:10 + @test @inferred assortativity(wheel_graph(n)) ≈ -1/3 + end + @testset "Directed ($seed)" for seed in [1, 2, 3], (n, ne) in [(14, 18), (10, 22), (7, 16)] + g = erdos_renyi(n, ne; is_directed=true, seed=seed) + assort = assortativity(g) + x = [outdegree(g, src(d))-1 for d in edges(g)] + y = [indegree(g, dst(d))-1 for d in edges(g)] + @test @inferred assort ≈ cor(x, y) + end + @testset "Undirected ($seed)" for seed in [1, 2, 3], (n, ne) in [(14, 18), (10, 22), (7, 16)] + g = erdos_renyi(n, ne; is_directed=false, seed=seed) + assort = assortativity(g) + x = [outdegree(g, src(d))-1 for d in edges(g)] + y = [indegree(g, dst(d))-1 for d in edges(g)] + @test @inferred assort ≈ cor([x;y], [y;x]) + end +end diff --git a/test/linalg/spectral.jl b/test/linalg/spectral.jl index 593229db2..e24315128 100644 --- a/test/linalg/spectral.jl +++ b/test/linalg/spectral.jl @@ -129,6 +129,16 @@ Matrix(nbt::Nonbacktracking) = Matrix(sparse(nbt)) @test incidence_matrix(g; oriented=true)[2, 1] == 1 @test incidence_matrix(g; oriented=true)[3, 1] == 0 end + + # Check size of incidence matrix with isolated vertices + g6 = complete_graph(4) + for k = 1:3 + add_vertex!(g6) + end + for g in testgraphs(g6) + @test size(incidence_matrix(g6)) == (7, 6) + end + # TESTS FOR Nonbacktracking operator. n = 10; k = 5 diff --git a/test/runtests.jl b/test/runtests.jl index ddfe24d3b..295e400ef 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -59,6 +59,7 @@ tests = [ "community/modularity", "community/clustering", "community/clique_percolation", + "community/assortativity", "centrality/betweenness", "centrality/closeness", "centrality/degree", diff --git a/test/shortestpaths/dijkstra.jl b/test/shortestpaths/dijkstra.jl index 43695597d..b1bb4945d 100644 --- a/test/shortestpaths/dijkstra.jl +++ b/test/shortestpaths/dijkstra.jl @@ -17,6 +17,9 @@ @test @inferred(enumerate_paths(z)) == enumerate_paths(y) @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z, 4) == + # test that we can pass a range into enumerate_paths - previously this caused + # infinite recursion - see #1552 + enumerate_paths(z, 3:4)[2] == enumerate_paths(y, 4) == [2, 3, 4] end