From c93b73ae0de471dc905243827b8f902f980031dc Mon Sep 17 00:00:00 2001 From: Seth Bromberger Date: Wed, 29 Mar 2017 09:41:59 -0700 Subject: [PATCH] WIP: LightGraphs Abstraction (#541) * benchmarks * add edgetype benchmark katz centrality is broken. * simplegraphs abstraction * Edge is no longer a Pair * pkgbenchmarks * f * remove data files from benchmarks * simplegraphs, take 2 * more changes * reshuffle * fix tests * more tests * abstractions * more tests * tests and fixes * trait fixes and tests - unrolling * persistence and floyd-warshall * make(di)graphs, through spanningtrees * moved cliques, testing through connectivity.jl * @jpfairbanks first round of review * another fix * all tests * new simpletraits * first cut at 0.6 compat * squash * update randgraphs.jl to use Channels over Tasks Fixes deprecation warnings introduced in: https://github.com/JuliaLang/julia/pull/19841 Changes an API interface: -function Graph(nvg::Int, neg::Int, edgestream::Task) +function Graph(nvg::Int, neg::Int, edgestream::Channel) Iteration over Tasks is deprecated so now we iterate over the Channel. * got rid of tasks in randgraphs * graph -> g * Add tutorials to section on docs (#547) * Update README.md * Update README.md Made tutorials separate line and consistent with the other lines. * type -> mutable struct * more type -> mutable struct, plus OF detection for add_vertex! * foo{T}(x::T) -> foo(x::T) where T * test negative cycles * test coverage * manual cherry-pick of #551 * simplegraph/ -> simplegraphs, optimization for is_connected, some type simplifications * re-add b-f tests * Inferred (#554) * core * @inferred wherever possible * empty -> zero * test grid periodic=true * oops * redo graphmatrices tests * linalg test fix * loosen type restrictions in randgraphs functions * readall -> readstring, and comment rationalization in randgraphs * Fixes #555: graphmatrices convert incorrect on CA (#560) CombinatorialAdjacency(CombinatorialAdjacency(g)) was returning the backing storage. Fix includes tests. * fixes #564 * one more test * removed nv() and vertices() (#565) * simpleedge tests * test coverage * short circuit B-F negative cycles, plus tests * more test coverage * more test coverage * Docs (#567) * docs, plus some various fixes (see blocking_flow). * nodes -> vertices, node -> vertex, and more doc consistency * doc fixes * 1.0 -> 0.8 * docfix and benchmarks * doc fixes --- .travis.yml | 4 +- CONTRIBUTING.md | 2 +- README.md | 129 ++--- REQUIRE | 3 +- benchmark/edges.jl | 3 +- benchmark/max-flow.jl | 2 +- benchmarks/core.jl | 41 ++ src/LightGraphs.jl | 46 +- src/biconnectivity/articulation.jl | 56 ++- src/biconnectivity/biconnect.jl | 60 ++- src/centrality/betweenness.jl | 80 ++- src/centrality/closeness.jl | 15 +- src/centrality/degree.jl | 46 +- src/centrality/katz.jl | 9 +- src/centrality/pagerank.jl | 24 +- src/community/cliques.jl | 39 +- src/community/clustering.jl | 90 ++-- src/community/core-periphery.jl | 10 +- src/community/label_propagation.jl | 74 +-- src/community/modularity.jl | 8 +- src/connectivity.jl | 237 +++++---- src/core.jl | 331 +++++-------- src/deprecations.jl | 2 + src/digraph-transitivity.jl | 53 +- src/digraph.jl | 119 ----- src/distance.jl | 100 ++-- src/edgeiter.jl | 76 --- src/edit_distance.jl | 59 ++- src/flow/boykov_kolmogorov.jl | 291 ++++++----- src/flow/dinic.jl | 115 ++--- src/flow/edmonds_karp.jl | 148 +++--- src/flow/ext_multiroute_flow.jl | 409 ++++++++-------- src/flow/kishimoto.jl | 115 +++-- src/flow/maximum_flow.jl | 187 ++++--- src/flow/multiroute_flow.jl | 299 ++++++------ src/flow/push_relabel.jl | 209 ++++---- src/generators/euclideangraphs.jl | 48 +- src/generators/randgraphs.jl | 456 +++++++++++------- src/generators/smallgraphs.jl | 371 +++++++------- src/generators/staticgraphs.jl | 152 ++++-- src/graph.jl | 139 ------ src/graphtypes/simplegraphs/SimpleGraphs.jl | 152 ++++++ src/graphtypes/simplegraphs/simpledigraph.jl | 151 ++++++ src/graphtypes/simplegraphs/simpleedge.jl | 31 ++ src/graphtypes/simplegraphs/simpleedgeiter.jl | 80 +++ src/graphtypes/simplegraphs/simplegraph.jl | 171 +++++++ src/interface.jl | 188 ++++++++ src/linalg/graphmatrices.jl | 346 +++++++------ src/linalg/nonbacktracking.jl | 58 ++- src/linalg/spectral.jl | 129 +++-- src/matching/README.md | 2 - src/operators.jl | 234 +++++---- src/persistence/common.jl | 31 +- src/persistence/dot.jl | 4 +- src/persistence/gexf.jl | 13 +- src/persistence/gml.jl | 17 +- src/persistence/graph6.jl | 21 +- src/persistence/graphml.jl | 4 +- src/persistence/jld.jl | 17 +- src/persistence/lg.jl | 20 +- src/persistence/net.jl | 43 +- src/shortestpaths/astar.jl | 49 +- src/shortestpaths/bellman-ford.jl | 86 ++-- src/shortestpaths/dijkstra.jl | 62 +-- src/shortestpaths/floyd-warshall.jl | 47 +- src/spanningtrees/kruskal.jl | 45 +- src/spanningtrees/prim.jl | 40 +- src/traversals/bfs.jl | 153 +++--- src/traversals/dfs.jl | 84 ++-- src/traversals/graphvisit.jl | 48 +- src/traversals/maxadjvisit.jl | 86 ++-- src/traversals/randomwalks.jl | 92 ++-- src/utils.jl | 19 +- test/biconnectivity/articulation.jl | 61 +-- test/biconnectivity/biconnect.jl | 48 +- test/centrality/betweenness.jl | 37 +- test/centrality/closeness.jl | 25 +- test/centrality/degree.jl | 10 +- test/centrality/katz.jl | 8 +- test/centrality/pagerank.jl | 10 +- test/{ => community}/cliques.jl | 25 +- test/community/clustering.jl | 12 +- test/community/core-periphery.jl | 30 +- test/community/label_propagation.jl | 24 +- test/community/modularity.jl | 14 +- test/connectivity.jl | 178 ++++--- test/core.jl | 241 +++------ test/distance.jl | 41 +- test/edgeiter.jl | 52 -- test/edit_distance.jl | 43 +- test/flow/boykov_kolmogorov.jl | 43 +- test/flow/dinic.jl | 78 +-- test/flow/edmonds_karp.jl | 91 ++-- test/flow/maximum_flow.jl | 85 ++-- test/flow/multiroute_flow.jl | 142 +++--- test/flow/push_relabel.jl | 112 ++--- test/generators/euclideangraphs.jl | 4 +- test/generators/randgraphs.jl | 24 +- test/generators/staticgraphs.jl | 45 +- test/graphdigraph.jl | 77 --- test/graphtypes/simplegraphs/simpleedge.jl | 29 ++ .../graphtypes/simplegraphs/simpleedgeiter.jl | 55 +++ test/graphtypes/simplegraphs/simplegraphs.jl | 159 ++++++ test/interface.jl | 38 ++ test/linalg/graphmatrices.jl | 108 ++--- test/linalg/spectral.jl | 215 +++++---- test/operators.jl | 358 ++++++++------ test/persistence/persistence.jl | 5 + test/runtests.jl | 87 ++-- test/shortestpaths/astar.jl | 13 +- test/shortestpaths/bellman-ford.jl | 52 +- test/shortestpaths/dijkstra.jl | 67 +-- test/shortestpaths/floyd-warshall.jl | 13 +- test/spanningtrees/kruskal.jl | 25 +- test/spanningtrees/prim.jl | 20 +- test/transitivity.jl | 44 +- test/traversals/bfs.jl | 101 ++-- test/traversals/dfs.jl | 23 +- test/traversals/graphvisit.jl | 45 +- test/traversals/maxadjvisit.jl | 34 +- test/traversals/randomwalks.jl | 74 +-- test/utils.jl | 6 +- 122 files changed, 5689 insertions(+), 4622 deletions(-) create mode 100644 benchmarks/core.jl create mode 100644 src/deprecations.jl delete mode 100644 src/digraph.jl delete mode 100644 src/edgeiter.jl delete mode 100644 src/graph.jl create mode 100644 src/graphtypes/simplegraphs/SimpleGraphs.jl create mode 100644 src/graphtypes/simplegraphs/simpledigraph.jl create mode 100644 src/graphtypes/simplegraphs/simpleedge.jl create mode 100644 src/graphtypes/simplegraphs/simpleedgeiter.jl create mode 100644 src/graphtypes/simplegraphs/simplegraph.jl create mode 100644 src/interface.jl delete mode 100644 src/matching/README.md rename test/{ => community}/cliques.jl (64%) delete mode 100644 test/edgeiter.jl delete mode 100644 test/graphdigraph.jl create mode 100644 test/graphtypes/simplegraphs/simpleedge.jl create mode 100644 test/graphtypes/simplegraphs/simpleedgeiter.jl create mode 100644 test/graphtypes/simplegraphs/simplegraphs.jl create mode 100644 test/interface.jl diff --git a/.travis.yml b/.travis.yml index 9810d63af..0b2c04468 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ os: # - osx julia: - - 0.5 -# - nightly + # - 0.5 + - nightly notifications: email: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a61e07127..07a280b2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,7 @@ function f(g::AbstractGraph, v::Integer) return inner!(storage, g, v) end -function inner!(storage::AbstractArray{Int,1}, g::AbstractGraph, v::Integer) +function inner!(storage::AbstractVector{Int}, g::AbstractGraph, v::Integer) # some code operating on storage, g, and v. for i in 1:nv(g) storage[i] = v-i diff --git a/README.md b/README.md index 27514835a..48ebf697b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ better-optimized mechanisms. Additional functionality may be found in the companion package [LightGraphsExtras.jl](https://github.com/JuliaGraphs/LightGraphsExtras.jl). + ## Documentation Full documentation is available at [GitHub Pages](https://juliagraphs.github.io/LightGraphs.jl/latest). Documentation for methods is also available via the Julia REPL help system. @@ -38,33 +39,19 @@ A graph *G* is described by a set of vertices *V* and edges *E*: *G = {V, E}*. *V* is an integer range `1:n`; *E* is represented as forward (and, for directed graphs, backward) adjacency lists indexed by vertices. Edges may also be accessed via an iterator that yields `Edge` types containing -`(src::Int, dst::Int)` values. +`(src<:Integer, dst<:Integer)` values. Both vertices and edges may be integers +of any type, and the smallest type that fits the data is recommended in order +to save memory. *LightGraphs.jl* provides two graph types: `Graph` is an undirected graph, and `DiGraph` is its directed counterpart. Graphs are created using `Graph()` or `DiGraph()`; there are several options -(see below for examples). - -Edges are added to a graph using `add_edge!(g, e)`. Instead of an edge type -integers may be passed denoting the source and destination vertices (e.g., -`add_edge!(g, 1, 2)`). +(see the tutorials for examples). Multiple edges between two given vertices are not allowed: an attempt to add an edge that already exists in a graph will result in a silent failure. -Edges may be removed using `rem_edge!(g, e)`. Alternately, integers may be passed -denoting the source and destination vertices (e.g., `rem_edge!(g, 1, 2)`). Note -that, particularly for very large graphs, edge removal is a (relatively) -expensive operation. An attempt to remove an edge that does not exist in the graph will result in an -error. - -Use `nv(g)` and `ne(g)` to compute the number of vertices and edges respectively. - -`rem_vertex!(g, v)` alters the vertex identifiers. In particular, calling `n=nv(g)`, it swaps `v` and `n` and then removes `n`. - -`edges(g)` returns an iterator to the edge set. Use `collect(edge(set))` to fill -an array with all edges in the graph. ## Installation Installation is straightforward: @@ -72,57 +59,6 @@ Installation is straightforward: julia> Pkg.add("LightGraphs") ``` -## Usage Examples -(all examples apply equally to `DiGraph` unless otherwise noted): - -```julia -# create an empty undirected graph -g = Graph() - -# create a 10-node undirected graph with no edges -g = Graph(10) -@assert nv(g) == 10 - -# create a 10-node undirected graph with 30 randomly-selected edges -g = Graph(10,30) - -# add an edge between vertices 4 and 5 -add_edge!(g, 4, 5) - -# remove an edge between vertices 9 and 10 -rem_edge!(g, 9, 10) - -# create vertex 11 -add_vertex!(g) - -# remove vertex 2 -# attention: this changes the id of vertex nv(g) to 2 -rem_vertex!(g, 2) - -# get the neighbors of vertex 4 -neighbors(g, 4) - -# iterate over the edges -m = 0 -for e in edges(g) - m += 1 -end -@assert m == ne(g) - -# show distances between vertex 4 and all other vertices -dijkstra_shortest_paths(g, 4).dists - -# as above, but with non-default edge distances -distmx = zeros(10,10) -distmx[4,5] = 2.5 -distmx[5,4] = 2.5 -dijkstra_shortest_paths(g, 4, distmx).dists - -# graph I/O -g = loadgraph("mygraph.jgz", :lg) -savegraph("mygraph.gml", g, :gml) -``` - ## Current functionality - **core functions:** vertices and edges addition and removal, degree (in/out/histogram), neighbors (in/out/all/common) @@ -155,44 +91,50 @@ symmetric difference, blkdiag, induced subgraphs, products (cartesian/scalar) - **community:** modularity, community detection, core-periphery, clustering coefficients -- **persistence formats:** proprietary compressed, [GraphML](http://en.wikipedia.org/wiki/GraphML), [GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language), [Gexf](http://gexf.net/format), [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)), [Pajek NET](http://gephi.org/users/supported-graph-formats/pajek-net-format/) +- **persistence formats:** proprietary compressed, [GraphML](http://en.wikipedia.org/wiki/GraphML), [GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language), [Gexf](http://gexf.net/format), [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)), [Pajek NET](http://gephi.org/users/supported-graph-formats/pajek-net-format/), [Graph6](http://users.cecs.anu.edu.au/~bdm/data/formats.html) -- **visualization:** integration with [GraphLayout](https://github.com/IainNZ/GraphLayout.jl), [TikzGraphs](https://github.com/sisl/TikzGraphs.jl), [GraphPlot](https://github.com/JuliaGraphs/GraphPlot.jl), [NetworkViz](https://github.com/abhijithanilkumar/NetworkViz.jl/) +- **visualization:** integration with +[GraphPlot](https://github.com/JuliaGraphs/GraphPlot.jl), +[Plots](https://github.com/JuliaPlots/Plots.jl) via [PlotRecipes](https://github.com/JuliaPlots/PlotRecipes.jl), [GraphLayout](https://github.com/IainNZ/GraphLayout.jl), [TikzGraphs](https://github.com/sisl/TikzGraphs.jl), [NetworkViz](https://github.com/abhijithanilkumar/NetworkViz.jl/) ## Core API -These functions are defined as the public contract of the LightGraphs.AbstractGraph interface. +These functions are defined as the public contract of the `LightGraphs.AbstractGraph` interface. ### Constructing and modifying the graph - -- add_edge! -- rem_edge! -- add_vertex! -- add_vertices! -- rem_vertex! +- `Graph` +- `DiGraph` +- `add_edge!` +- `rem_edge!` +- `add_vertex!`, `add_vertices!` +- `rem_vertex!` +- `zero` ### Edge/Arc interface -- src -- dst +- `src` +- `dst` +- `reverse` +- `==` +- Pair / Tuple conversion ### Accessing state -- nv::Int -- ne::Int -- vertices (Iterable) -- edges (Iterable) -- neighbors -- in_edges -- out_edges -- has_vertex -- has_edge -- has_self_loops (though this might be a trait or an abstract graph type) +- `nv` +- `ne` +- `vertices` (Iterable) +- `edges` (Iterable) +- `neighbors`, `in_neighbors`, `out_neighbors` +- `in_edges` +- `out_edges` +- `has_vertex` +- `has_edge` +- `has_self_loops` (though this might be a trait or an abstract graph type) ### Non-Core APIs These functions can be constructed from the Core API functions but can be given specialized implementations in order to improve performance. -- adjacency_matrix -- degree +- `adjacency_matrix` +- `degree` This can be computed from neighbors by default `degree(g,v) = length(neighbors(g,v))` so you don't need to implement this unless your type can compute degree faster than this method. @@ -202,7 +144,8 @@ This can be computed from neighbors by default `degree(g,v) = length(neighbors(g * Julia 0.3: LightGraphs v0.3.7 is the last version guaranteed to work with Julia 0.3. * Julia 0.4: LightGraphs versions in the 0.6 series are designed to work with Julia 0.4. * Julia 0.5: LightGraphs versions in the 0.7 series are designed to work with Julia 0.5. -* Julia 0.6: Some functionality might not work with prerelease / unstable / nightly versions of Julia. If you run into a problem on 0.6, please file an issue. +* Julia 0.6: LightGraphs versions in the 0.8 series are designed to work with Julia 0.6. +* Later versions: Some functionality might not work with prerelease / unstable / nightly versions of Julia. If you run into a problem, please file an issue. # Contributing and Reporting Bugs We welcome contributions and bug reports! Please see [CONTRIBUTING.md](https://github.com/JuliaGraphs/LightGraphs.jl/blob/master/CONTRIBUTING.md) diff --git a/REQUIRE b/REQUIRE index 5662fda19..4da924ef8 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ -julia 0.5 +julia 0.6- GZip 0.2.20 EzXML 0.3.0 ParserCombinator 1.7.11 @@ -6,3 +6,4 @@ JLD 0.6.3 Distributions 0.10.2 StatsBase 0.9.0 DataStructures 0.5.0 +SimpleTraits 0.4.0 diff --git a/benchmark/edges.jl b/benchmark/edges.jl index 1bb5bf892..491170bc0 100644 --- a/benchmark/edges.jl +++ b/benchmark/edges.jl @@ -1,5 +1,6 @@ import Base: convert -typealias P Pair{Int, Int} + +const P = Pair{Int, Int} convert(::Type{Tuple}, e::Pair) = (e.first, e.second) diff --git a/benchmark/max-flow.jl b/benchmark/max-flow.jl index bfb5fee58..46a0f10cd 100644 --- a/benchmark/max-flow.jl +++ b/benchmark/max-flow.jl @@ -4,7 +4,7 @@ p = 8.0 / n A = sprand(n,n,p) g = DiGraph(A) - cap = round(A*100) + cap = round.(A*100) @bench "n = $n" LightGraphs.maximum_flow($g, 1, $n, $cap) end end # max-flow diff --git a/benchmarks/core.jl b/benchmarks/core.jl new file mode 100644 index 000000000..24fa36850 --- /dev/null +++ b/benchmarks/core.jl @@ -0,0 +1,41 @@ +bg = BenchmarkGroup() +SUITE["core"] = bg + +function bench_iteredges(g::AbstractGraph) + i = 0 + for e in edges(g) + i += 1 + end + return i +end + +function bench_has_edge(g::AbstractGraph) + srand(1) + nvg = nv(g) + srcs = rand([1:nvg;], cld(nvg, 4)) + dsts = rand([1:nvg;], cld(nvg, 4)) + i = 0 + for (s, d) in zip(srcs, dsts) + if has_edge(g, s, d) + i += 1 + end + end + return i +end + + +EDGEFNS = [ + bench_iteredges, + bench_has_edge +] + +for fun in EDGEFNS + for (name, g) in GRAPHS + bg["edges","$fun","graph","$name"] = @benchmarkable $fun($g) + end + + for (name, g) in DIGRAPHS + bg["edges","$fun","digraph","$name"] = @benchmarkable $fun($g) + end + +end diff --git a/src/LightGraphs.jl b/src/LightGraphs.jl index a4b2e3893..3793034ac 100644 --- a/src/LightGraphs.jl +++ b/src/LightGraphs.jl @@ -7,21 +7,25 @@ using DataStructures using EzXML using ParserCombinator: Parsers.DOT, Parsers.GML using StatsBase: fit, Histogram +using SimpleTraits import Base: write, ==, <, *, ≈, convert, isless, issubset, union, intersect, - reverse, reverse!, blkdiag, getindex, setindex!, show, print, copy, in, - sum, size, sparse, eltype, length, ndims, transpose, - ctranspose, join, start, next, done, eltype, get, issymmetric, A_mul_B! - + reverse, reverse!, blkdiag, isassigned, getindex, setindex!, show, + print, copy, in, sum, size, sparse, eltype, length, ndims, transpose, + ctranspose, join, start, next, done, eltype, get, issymmetric, A_mul_B!, + Pair, Tuple, zero +export +# Interface +AbstractGraph, AbstractDiGraph, AbstractEdge, AbstractEdgeInter, +Edge, Graph, DiGraph, vertices, edges, edgetype, nv, ne, src, dst, +is_directed, add_vertex!, add_edge!, rem_vertex!, rem_edge!, +has_vertex, has_edge, in_neighbors, out_neighbors, # core -export AbstractGraph, Edge, Graph, DiGraph, vertices, edges, src, dst, -fadj, badj, in_edges, out_edges, has_vertex, has_edge, is_directed, -nv, ne, add_edge!, rem_edge!, add_vertex!, add_vertices!, -indegree, outdegree, degree, degree_histogram, density, Δ, δ, -Δout, Δin, δout, δin, neighbors, in_neighbors, out_neighbors, -common_neighbors, all_neighbors, has_self_loops, num_self_loops, -rem_vertex!, +is_ordered, add_vertices!, indegree, outdegree, degree, +Δout, Δin, δout, δin, Δ, δ, degree_histogram, +neighbors, all_neighbors, common_neighbors, +has_self_loops, num_self_loops, density, squash, # distance eccentricity, diameter, periphery, radius, center, @@ -113,7 +117,10 @@ kruskal_mst, prim_mst, #biconnectivity and articulation points articulation, biconnected_components -"""An optimized graphs package. +""" + LightGraphs + +An optimized graphs package. Simple graphs (not multi- or hypergraphs) are represented in a memory- and time-efficient manner with adjacency lists and edge sets. Both directed and @@ -127,14 +134,21 @@ explicit design decision that any data not required for graph manipulation (attributes and other information, for example) is expected to be stored outside of the graph structure itself. Such data lends itself to storage in more traditional and better-optimized mechanisms. + +[Full documentation](http://codecov.io/github/JuliaGraphs/LightGraphs.jl) is available, +and tutorials are available at the +[JuliaGraphsTutorials repository](https://github.com/JuliaGraphs/JuliaGraphsTutorials). """ LightGraphs - +include("interface.jl") +include("deprecations.jl") include("core.jl") - include("digraph.jl") + include("graphtypes/simplegraphs/SimpleGraphs.jl") + const Graph = SimpleGraphs.SimpleGraph + const DiGraph = SimpleGraphs.SimpleDiGraph + const Edge = SimpleGraphs.SimpleEdge + include("digraph-transitivity.jl") - include("graph.jl") - include("edgeiter.jl") include("traversals/graphvisit.jl") include("traversals/bfs.jl") include("traversals/dfs.jl") diff --git a/src/biconnectivity/articulation.jl b/src/biconnectivity/articulation.jl index 211bfb7e1..9e8524a35 100644 --- a/src/biconnectivity/articulation.jl +++ b/src/biconnectivity/articulation.jl @@ -1,37 +1,29 @@ """ -Computes the articulation points(https://en.wikipedia.org/wiki/Biconnected_component) -of a connected graph `g` and returns an array containing all cut vertices. -""" -function articulation(g::AbstractGraph) :: AbstractArray{Int} - state = Articulations(g) - for u in vertices(g) - if state.depth[u] == 0 - visit!(state, g, u, u) - end - end - return [x for (x, y) in enumerate(state.articulation_points) if y == true] -end + Articulations{T} +A state type for the depth-first search that finds the articulation points in a graph. """ -Articulations: a state type for the Depth first search that finds the articulation points in a graph. -""" -type Articulations - low::Vector{Int} - depth::Vector{Int} - articulation_points::Vector{Bool} - id::Int +mutable struct Articulations{T<:Integer} + low::Vector{T} + depth::Vector{T} + articulation_points::BitArray + id::T end function Articulations(g::AbstractGraph) n = nv(g) - return Articulations(zeros(Int, n), zeros(Int, n),zeros(Int, n), 0) + T = typeof(n) + return Articulations(zeros(T, n), zeros(T, n), falses(n), zero(T)) end """ -Does a depth first search storing the depth (in `depth`) and low-points (in `low`) of each vertex. -Call this function repeatedly to complete the DFS see `articulation` for usage. + visit!(state, g, u, v) + +Perform a depth first search storing the depth (in `depth`) and low-points +(in `low`) of each vertex. +Call this function repeatedly to complete the DFS (see [`articulation`](@ref) for usage). """ -function visit!(state::Articulations, g::AbstractGraph, u::Int, v::Int) +function visit!(state::Articulations, g::AbstractGraph, u::Integer, v::Integer) children = 0 state.id += 1 state.depth[v] = state.id @@ -50,8 +42,24 @@ function visit!(state::Articulations, g::AbstractGraph, u::Int, v::Int) elseif w != u state.low[v] = min(state.low[v], state.depth[w]) end - end + end if u == v && children > 1 state.articulation_points[v] = true end end + +""" + articulation(g) + +Compute the [articulation points](https://en.wikipedia.org/wiki/Biconnected_component) +of a connected graph `g` and return an array containing all cut vertices. +""" +function articulation(g::AbstractGraph) + state = Articulations(g) + for u in vertices(g) + if state.depth[u] == 0 + visit!(state, g, u, u) + end + end + return find(state.articulation_points) +end diff --git a/src/biconnectivity/biconnect.jl b/src/biconnectivity/biconnect.jl index dbe5515f7..d7c1ec037 100644 --- a/src/biconnectivity/biconnect.jl +++ b/src/biconnectivity/biconnect.jl @@ -1,7 +1,9 @@ """ -Biconnections: A state type for Depth First Search that finds the biconnected components + Biconnections + +A state type for depth-first search that finds the biconnected components. """ -type Biconnections +mutable struct Biconnections low::Vector{Int} depth::Vector{Int} stack::Vector{Edge} @@ -9,36 +11,18 @@ type Biconnections id::Int end -function Biconnections(g::AbstractGraph) +@traitfn function Biconnections(g::::(!IsDirected)) n = nv(g) return Biconnections(zeros(Int, n), zeros(Int, n), Vector{Edge}(), Vector{Vector{Edge}}(), 0) end -""" -Computes the biconnected components of an undirected graph `g` -and returns a Vector of vectors containing each biconnected component. -(https://en.wikipedia.org/wiki/Biconnected_component).It's a DFS based linear time algorithm. -""" -function biconnected_components(g::Graph) - state = Biconnections(g) - for u in vertices(g) - if state.depth[u] == 0 - visit!(g, state, u, u) - end - - if !isempty(state.stack) - push!(state.biconnected_comps, reverse(state.stack)) - empty!(state.stack) - end - end - - return state.biconnected_comps -end """ -Does a DFS visit and stores the depth and low-points of each vertex + visit!(g, state, u, v) + +Perform a DFS visit storing the depth and low-points of each vertex. """ -function visit!(g::Graph, state::Biconnections, u::Int, v::Int) +function visit!(g::AbstractGraph, state::Biconnections, u::Integer, v::Integer) children = 0 state.id += 1 state.depth[v] = state.id @@ -68,3 +52,29 @@ function visit!(g::Graph, state::Biconnections, u::Int, v::Int) end end end + +@doc_str """ + biconnected_components(g) + +Compute the [biconnected components](https://en.wikipedia.org/wiki/Biconnected_component) +of an undirected graph `g`and return a vector of vectors containing each +biconnected component. + +Performance: +Time complexity is ``\\mathcal{O}(|V|)``. +""" +function biconnected_components end +@traitfn function biconnected_components(g::::(!IsDirected)) + state = Biconnections(g) + for u in vertices(g) + if state.depth[u] == 0 + visit!(g, state, u, u) + end + + if !isempty(state.stack) + push!(state.biconnected_comps, reverse(state.stack)) + empty!(state.stack) + end + end + return state.biconnected_comps +end diff --git a/src/centrality/betweenness.jl b/src/centrality/betweenness.jl index 6d07569ae..ab9da8ed7 100644 --- a/src/centrality/betweenness.jl +++ b/src/centrality/betweenness.jl @@ -2,60 +2,42 @@ # TODO - weighted, separate unweighted, edge betweenness -doc""" - betweenness_centrality(g, k=0; normalize=true, endpoints=false) +@doc_str """ + betweenness_centrality(g[, vs]) + betweenness_centrality(g, k) +Calculate the [betweenness centrality](https://en.wikipedia.org/wiki/Centrality#Betweenness_centrality) +of a graph `g` across all vertices, a specified subset of vertices `vs`, or a random subset of `k` +vertices. Return a vector representing the centrality calculated for each node in `g`. -Calculates the [betweenness centrality](https://en.wikipedia.org/wiki/Centrality#Betweenness_centrality) of -the graph `g`, or, optionally, of a random subset of `k` vertices. Can -optionally include endpoints in the calculations. Normalization is enabled by -default. +### Optional Arguments +- `normalize=true`: If true, normalize the betweenness values by the +total number of possible distinct paths between all pairsin the graphs. +For an undirected graph, this number is ``\\frac{(|V|-1)(|V|-2)}{2}`` +and for a directed graph, ``\\frac{(|V|-1)(|V|-2)}``. +- `endpoints=false`: If true, include endpoints in the shortest path count. Betweenness centrality is defined as: +`` +bc(v) = \\frac{1}{\\mathcal{N}} \sum_{s \\neq t \\neq v} +\\frac{\\sigma_{st}(v)}{\\sigma_{st}} +``. -$bc(v) = \frac{1}{\mathcal{N}} \sum_{s \neq t \neq v} - \frac{\sigma_{st}(v)}{\sigma_{st}}$. - - **Parameters** - -g: AbstractGraph - A Graph, directed or undirected. - -k: Integer, optional - Use `k` nodes sample to estimate the betweenness centrality. If none, - betweenness centrality is computed using the `n` nodes in the graph. - -normalize: bool, optional - If true, the betweenness values are normalized by the total number - of possible distinct paths between all pairs in the graphs. For an undirected graph, - this number if `((n-1)*(n-2))/2` and for a directed graph, `(n-1)*(n-2)` - where `n` is the number of nodes in the graph. - -endpoints: bool, optional - If true, endpoints are included in the shortest path count. - -**Returns** - -betweenness: Array{Float64} - Betweenness centrality value per node id. - - -**References** - -[1] Brandes 2001 & Brandes 2008 +### References +- Brandes 2001 & Brandes 2008 """ function betweenness_centrality( g::AbstractGraph, - nodes::AbstractArray{Int, 1} = vertices(g); + vs::AbstractVector = vertices(g); normalize=true, endpoints=false) n_v = nv(g) - k = length(nodes) + k = length(vs) isdir = is_directed(g) betweenness = zeros(n_v) - for s in nodes + for s in vs if degree(g,s) > 0 # this might be 1? state = dijkstra_shortest_paths(g, s; allpaths=true) if endpoints @@ -67,16 +49,16 @@ function betweenness_centrality( end _rescale!(betweenness, - n_v, - normalize, - isdir, - k) + n_v, + normalize, + isdir, + k) return betweenness end -betweenness_centrality(g::AbstractGraph, k::Int; normalize=true, endpoints=false) = - betweenness_centrality(g, sample(vertices(g), k); normalize=normalize, endpoints=endpoints) +betweenness_centrality(g::AbstractGraph, k::Integer; normalize=true, endpoints=false) = +betweenness_centrality(g, sample(vertices(g), k); normalize=normalize, endpoints=endpoints) @@ -94,7 +76,7 @@ function _accumulate_basic!( # make sure the source index has no parents. P[si] = [] - # we need to order the source nodes by decreasing distance for this to work. + # we need to order the source vertices by decreasing distance for this to work. S = sortperm(state.dists, rev=true) for w in S coeff = (1.0 + δ[w]) / σ[w] @@ -109,8 +91,6 @@ function _accumulate_basic!( end end - - function _accumulate_endpoints!( betweenness::Vector{Float64}, state::DijkstraState, @@ -125,7 +105,7 @@ function _accumulate_endpoints!( v1 = [1:n_v;] v2 = state.dists S = sortperm(state.dists, rev=true) - s = g.vertices[si] + s = vertices(g)[si] betweenness[s] += length(S) - 1 # 289 for w in S @@ -139,7 +119,7 @@ function _accumulate_endpoints!( end end -function _rescale!(betweenness::Vector{Float64}, n::Int, normalize::Bool, directed::Bool, k::Int) +function _rescale!(betweenness::Vector{Float64}, n::Integer, normalize::Bool, directed::Bool, k::Int) if normalize if n <= 2 do_scale = false diff --git a/src/centrality/closeness.jl b/src/centrality/closeness.jl index 46125501b..2bd734525 100644 --- a/src/centrality/closeness.jl +++ b/src/centrality/closeness.jl @@ -1,5 +1,13 @@ -"""Calculates the [closeness centrality](https://en.wikipedia.org/wiki/Centrality#Closeness_centrality) -of the graph `g`. +@doc_str """ + closeness_centrality(g) + +Calculate the [closeness centrality](https://en.wikipedia.org/wiki/Centrality#Closeness_centrality) +of the graph `g`. Return a vector representing the centrality calculated for each node in `g`. + +### Optional Arguments +- `normalize=true`: If true, normalize the centrality value of each +node `n` by ``\\frac{|δ_n|}{|V|-1}, where ``δ_n`` is the set of vertices reachable +from node `n`. """ function closeness_centrality( g::AbstractGraph; @@ -8,7 +16,7 @@ function closeness_centrality( n_v = nv(g) closeness = zeros(n_v) - for u = 1:n_v + for u in vertices(g) if degree(g, u) == 0 # no need to do Dijkstra here closeness[u] = 0.0 else @@ -18,7 +26,6 @@ function closeness_centrality( l = length(δ) - 1 if σ > 0 closeness[u] = l / σ - if normalize n = l / (n_v-1) closeness[u] *= n diff --git a/src/centrality/degree.jl b/src/centrality/degree.jl index fe069134f..b0b267612 100644 --- a/src/centrality/degree.jl +++ b/src/centrality/degree.jl @@ -1,27 +1,31 @@ function _degree_centrality(g::AbstractGraph, gtype::Integer; normalize=true) - n_v = nv(g) - c = zeros(n_v) - for v in 1:n_v - if gtype == 0 # count both in and out degree if appropriate - deg = outdegree(g, v) + (typeof(g) == DiGraph? indegree(g, v) : 0.0) - elseif gtype == 1 # count only in degree - deg = indegree(g, v) - else # count only out degree - deg = outdegree(g, v) - end - s = normalize? (1.0 / (n_v - 1.0)) : 1.0 - c[v] = deg*s - end - return c + n_v = nv(g) + c = zeros(n_v) + for v in vertices(g) + if gtype == 0 # count both in and out degree if appropriate + deg = is_directed(g)? outdegree(g, v) + indegree(g, v) : outdegree(g, v) + elseif gtype == 1 # count only in degree + deg = indegree(g, v) + else # count only out degree + deg = outdegree(g, v) + end + s = normalize? (1.0 / (n_v - 1.0)) : 1.0 + c[v] = deg*s + end + return c end -# TODO avoid repetition of this docstring -"""Calculates the [degree centrality](https://en.wikipedia.org/wiki/Centrality#Degree_centrality) -of the graph `g`, with optional (default) normalization.""" +""" + degree_centrality(g) + indegree_centrality(g) + outdegree_centrality(g) + +Calculate the [degree centrality](https://en.wikipedia.org/wiki/Centrality#Degree_centrality) +of graph `g`. Return a vector representing the centrality calculated for each node in `g`. + +### Optional Arguments +- `normalize=true`: If true, normalize each centrality measure by ``\frac{1}{|V|-1}``. +""" degree_centrality(g::AbstractGraph; all...) = _degree_centrality(g, 0; all...) -"""Calculates the [degree centrality](https://en.wikipedia.org/wiki/Centrality#Degree_centrality) -of the graph `g`, with optional (default) normalization.""" indegree_centrality(g::AbstractGraph; all...) = _degree_centrality(g, 1; all...) -"""Calculates the [degree centrality](https://en.wikipedia.org/wiki/Centrality#Degree_centrality) -of the graph `g`, with optional (default) normalization.""" outdegree_centrality(g::AbstractGraph; all...) = _degree_centrality(g, 2; all...) diff --git a/src/centrality/katz.jl b/src/centrality/katz.jl index adac43fab..720226df0 100644 --- a/src/centrality/katz.jl +++ b/src/centrality/katz.jl @@ -21,10 +21,13 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Calculates the [Katz centrality](https://en.wikipedia.org/wiki/Katz_centrality) -of the graph `g`. """ -function katz_centrality(g::AbstractGraph, α::Real = 0.3) + katz_centrality(g, α=0.3) +Calculate the [Katz centrality](https://en.wikipedia.org/wiki/Katz_centrality) +of the graph `g` optionally parameterized by `α`. Return a vector representing +the centrality calculated for each node in `g`. +""" +function katz_centrality(g::AbstractGraph, α::Real=0.3) nvg = nv(g) v = ones(Float64, nvg) spI = speye(Float64, nvg) diff --git a/src/centrality/pagerank.jl b/src/centrality/pagerank.jl index 4ef1c1494..df860ce77 100644 --- a/src/centrality/pagerank.jl +++ b/src/centrality/pagerank.jl @@ -1,28 +1,34 @@ # Parts of this code were taken / derived from NetworkX. See LICENSE for # licensing details. -"""Calculates the [PageRank](https://en.wikipedia.org/wiki/PageRank) of the graph -`g`. Can optionally specify a different damping factor (`α`), number of -iterations (`n`), and convergence threshold (`ϵ`). If convergence is not -reached within `n` iterations, an error will be returned. """ -function pagerank(g::DiGraph, α=0.85, n=100, ϵ = 1.0e-6) + pagerank(g, α=0.85, n=100, ϵ=1.0e-6) + +Calculate the [PageRank](https://en.wikipedia.org/wiki/PageRank) of the +directed graph `g` parameterized by damping factor `α`, number of +iterations `n`, and convergence threshold `ϵ`. Return a vector representing +the centrality calculated for each node in `g`, or an error if convergence +is not reached within `n` iterations. +""" +function pagerank end + +@traitfn function pagerank(g::::IsDirected, α=0.85, n=100, ϵ=1.0e-6) A = adjacency_matrix(g,:in,Float64) S = vec(sum(A,1)) S = 1./S S[find(S .== Inf)]=0.0 M = A' # need a separate line due to bug #17456 in julia M = (Diagonal(S) * M)' - N = nv(g) - x = repmat([1.0/N], N) - p = repmat([1.0/N], N) + N = Int(nv(g)) + x = fill(1.0/N, N) + p = fill(1.0/N, N) dangling_weights = p is_dangling = find(S .== 0) for _ in 1:n xlast = x x = α * (M * x + sum(x[is_dangling]) * dangling_weights) + (1 - α) * p - err = sum(abs(x - xlast)) + err = sum(abs, (x - xlast)) if (err < N * ϵ) return x end diff --git a/src/community/cliques.jl b/src/community/cliques.jl index d198eee16..30a72f16f 100644 --- a/src/community/cliques.jl +++ b/src/community/cliques.jl @@ -6,9 +6,12 @@ ################################################################## """ -Finds all maximal cliques of an undirected graph. + maximal_cliques(g) -``` +Return a vector of vectors representing the node indices in each of the maximal +cliques found in the undirected graph `g`. + +```jldoctest julia> using LightGraphs julia> g = Graph(3) julia> add_edge!(g, 1, 2) @@ -19,20 +22,20 @@ julia> maximal_cliques(g) [2,1] ``` """ -function maximal_cliques(g::Graph) - - +function maximal_cliques end +@traitfn function maximal_cliques(g::::(!IsDirected)) + T = eltype(g) # Cache nbrs and find first pivot (highest degree) maxconn = -1 - nnbrs = Vector{Set{Int}}() + nnbrs = Vector{Set{T}}() for n in vertices(g) - push!(nnbrs, Set{Int}()) + push!(nnbrs, Set{T}()) end - pivotnbrs = Set{Int}() # handle empty graph - pivotdonenbrs = Set{Int}() # initialize + pivotnbrs = Set{T}() # handle empty graph + pivotdonenbrs = Set{T}() # initialize for n in vertices(g) - nbrs = Set{Int}() + nbrs = Set{T}() union!(nbrs, out_neighbors(g, n)) delete!(nbrs, n) # ignore edges between n and itself conn = length(nbrs) @@ -46,17 +49,17 @@ function maximal_cliques(g::Graph) end # Initial setup - cand = Set{Int}(vertices(g)) + cand = Set{T}(vertices(g)) # union!(cand, keys(nnbrs)) smallcand = setdiff(cand, pivotnbrs) - done = Set{Int}() - stack = Vector{Tuple{Set{Int}, Set{Int}, Set{Int}}}() - clique_so_far = Vector{Int}() - cliques = Vector{Array{Int}}() + done = Set{T}() + stack = Vector{Tuple{Set{T}, Set{T}, Set{T}}}() + clique_so_far = Vector{T}() + cliques = Vector{Array{T}}() # Start main loop while !isempty(smallcand) || !isempty(stack) - if !isempty(smallcand) # Any nodes left to check? + if !isempty(smallcand) # Any vertices left to check? n = pop!(smallcand) else # back out clique_so_far @@ -87,7 +90,7 @@ function maximal_cliques(g::Graph) continue end # find pivot node (max connected in cand) - # look in done nodes first + # look in done vertices first numb_cand = length(new_cand) maxconndone = -1 for n in new_done @@ -107,7 +110,7 @@ function maximal_cliques(g::Graph) continue end # still finding pivot node - # look in cand nodes second + # look in cand vertices second maxconn = -1 for n in new_cand cn = intersect(new_cand, nnbrs[n]) diff --git a/src/community/clustering.jl b/src/community/clustering.jl index 51131cb66..ef31fb250 100644 --- a/src/community/clustering.jl +++ b/src/community/clustering.jl @@ -1,93 +1,83 @@ """ local_clustering_coefficient(g, v) + local_clustering_coefficient(g, vs) -Computes the [local clustering coefficient](https://en.wikipedia.org/wiki/Clustering_coefficient) for node `v`. +Return the [local clustering coefficient](https://en.wikipedia.org/wiki/Clustering_coefficient) +for node `v` in graph `g`. If a list of vertices `vs` is specified, return a vector +of coefficients for each node in the list. """ function local_clustering_coefficient(g::AbstractGraph, v::Integer) ntriang, alltriang = local_clustering(g, v) - return alltriang == 0 ? 0. : ntriang / alltriang + return alltriang == 0 ? 0. : ntriang * 1.0 / alltriang end +local_clustering_coefficient(g::AbstractGraph, vs = vertices(g)) = + [local_clustering_coefficient(g, v) for v in vs] -""" + +@doc_str """ local_clustering(g, v) + local_clustering(g, vs) + +Return a tuple `(a, b)`, where `a` is the number of triangles in the neighborhood +of `v` and `b` is the maximum number of possible triangles. If a list of vertices +`vs` is specified, return two vectors representing the number of triangles and +the maximum number of possible triangles, respectively, for each node in the list. -Returns a tuple `(a,b)`, where `a` is the number of triangles in the neighborhood of -`v` and `b` is the maximum number of possible triangles. -It is related to the local clustering coefficient by `r=a/b`. +This function is related to the local clustering coefficient `r` by ``r=\frac{a}{b}``. """ function local_clustering(g::AbstractGraph, v::Integer) k = degree(g, v) k <= 1 && return (0, 0) neighs = neighbors(g, v) c = 0 - for i=1:length(neighs) - for j=1:length(neighs) - i == j && continue - if has_edge(g, neighs[i], neighs[j]) - c += 1 - end + for i in neighs, j in neighs + i == j && continue + if has_edge(g, i, j) + c += 1 end end return is_directed(g) ? (c , k*(k-1)) : (div(c,2) , div(k*(k-1),2)) end - -""" - triangles(g, v) - -Returns the number of triangles in the neighborhood for node `v`. -""" -triangles(g::AbstractGraph, v::Integer) = local_clustering(g, v)[1] - - -""" - local_clustering_coefficient(g, vlist = vertices(g)) - -Returns a vector containing the [local clustering coefficients](https://en.wikipedia.org/wiki/Clustering_coefficient) for vertices `vlist`. -""" -local_clustering_coefficient(g::AbstractGraph, vlist = vertices(g)) = Float64[local_clustering_coefficient(g, v) for v in vlist] - -""" - local_clustering(g, vlist = vertices(g)) - -Returns two vectors, respectively containing the first and second result of `local_clustering_coefficients(g, v)` -for each `v` in `vlist`. -""" -function local_clustering(g::AbstractGraph, vlist = vertices(g)) - ntriang = zeros(Int, length(vlist)) - nalltriang = zeros(Int, length(vlist)) +function local_clustering(g::AbstractGraph, vs = vertices(g)) + ntriang = zeros(Int, length(vs)) + nalltriang = zeros(Int, length(vs)) i = 0 - for v in vlist - i+=1 + for (i, v) in enumerate(vs) ntriang[i], nalltriang[i] = local_clustering(g, v) end return ntriang, nalltriang end + """ - triangles(g, vlist = vertices(g)) + triangles(g[, v]) + triangles(g, vs) -Returns a vector containing the number of triangles for vertices `vlist`. +Return the number of triangles in the neighborhood of node `v` in graph `g`. +If a list of vertices `vs` is specified, return a vector of number of triangles +for each node in the list. If no vertices are specified, return the number +of triangles for each node in the graph. """ -triangles(g::AbstractGraph, vlist = vertices(g)) = local_clustering(g, vlist)[1] +triangles(g::AbstractGraph, v::Integer) = local_clustering(g, v)[1] +triangles(g::AbstractGraph, vs = vertices(g)) = local_clustering(g, vs)[1] """ global_clustering_coefficient(g) -Computes the [global clustering coefficient](https://en.wikipedia.org/wiki/Clustering_coefficient). +Return the [global clustering coefficient](https://en.wikipedia.org/wiki/Clustering_coefficient) +of graph `g`. """ function global_clustering_coefficient(g::AbstractGraph) c = 0 ntriangles = 0 - for v in 1:nv(g) + for v in vertices(g) neighs = neighbors(g, v) - for i=1:length(neighs) - for j=1:length(neighs) - i == j && continue - if has_edge(g, neighs[i], neighs[j]) - c += 1 - end + for i in neighs, j in neighs + i == j && continue + if has_edge(g, i, j) + c += 1 end end k = degree(g, v) diff --git a/src/community/core-periphery.jl b/src/community/core-periphery.jl index d3ad65119..7831a85ac 100644 --- a/src/community/core-periphery.jl +++ b/src/community/core-periphery.jl @@ -1,10 +1,14 @@ """ core_periphery_deg(g) -A simple degree-based core-periphery detection algorithm (see [Lip](http://arxiv.org/abs/1102.5511)). -Returns the vertex assignments (1 for core and 2 for periphery). +Compute the degree-based core-periphery for graph `g`. Return the vertex +assignments (`1` for core and `2` for periphery) for each node in `g`. + +References: + [Lip](http://arxiv.org/abs/1102.5511)) """ -function core_periphery_deg(g::Graph) +function core_periphery_deg end +@traitfn function core_periphery_deg(g::::(!IsDirected)) degs = degree(g) p = sortperm(degs, rev=true) s = sum(degs) / 2. diff --git a/src/community/label_propagation.jl b/src/community/label_propagation.jl index e343190b7..ebcba9ae7 100644 --- a/src/community/label_propagation.jl +++ b/src/community/label_propagation.jl @@ -1,24 +1,30 @@ """ -Community detection using the label propagation algorithm (see [Raghavan et al.](http://arxiv.org/abs/0709.2938)). -`g`: input Graph -`maxiter`: maximum number of iterations -return : vertex assignments and the convergence history + label_propagation(g, maxiter=1000) + +Community detection using the label propagation algorithm. +Return two vectors: the first is the label number assigned to each node, and +the second is the convergence history for each node. Will return after +`maxiter` iterations if convergence has not completed. + +### References +- [Raghavan et al.](http://arxiv.org/abs/0709.2938) """ -function label_propagation(g::AbstractGraph; maxiter=1000) +function label_propagation(g::AbstractGraph, maxiter=1000) + T = eltype(g) n = nv(g) - label = collect(1:n) - active_nodes = IntSet(vertices(g)) - c = NeighComm(collect(1:n), fill(-1,n), 1) + label = collect(one(T):n) + active_vs = IntSet(vertices(g)) + c = NeighComm(collect(one(T):n), fill(-1,n), one(T)) convergence_hist = Vector{Int}() - random_order = Array(Int, n) + random_order = Vector{T}(n) i = 0 - while !isempty(active_nodes) && i < maxiter - num_active = length(active_nodes) + while !isempty(active_vs) && i < maxiter + num_active = length(active_vs) push!(convergence_hist, num_active) i += 1 - # processing nodes in random order - for (j,node) in enumerate(active_nodes) + # processing vertices in random order + for (j,node) in enumerate(active_vs) random_order[j] = node end range_shuffle!(1:num_active, random_order) @@ -28,10 +34,10 @@ function label_propagation(g::AbstractGraph; maxiter=1000) label[u] = vote!(g, label, c, u) if old_comm != label[u] for v in out_neighbors(g, u) - push!(active_nodes, v) + push!(active_vs, v) end else - delete!(active_nodes, u) + delete!(active_vs, u) end end end @@ -40,14 +46,22 @@ function label_propagation(g::AbstractGraph; maxiter=1000) label, convergence_hist end -"""Type to record neighbor labels and their counts.""" -type NeighComm - neigh_pos::Vector{Int} - neigh_cnt::Vector{Int} - neigh_last::Int +""" + NeighComm{T} + +Type to record neighbor labels and their counts. +""" +mutable struct NeighComm{T<:Integer} + neigh_pos::Vector{T} + neigh_cnt::Vector{Int} + neigh_last::T end -"""Fast shuffle Array `a` in UnitRange `r` inplace.""" +""" + range_shuffle!(r, a) + +Fast shuffle Array `a` in UnitRange `r`. +""" function range_shuffle!(r::UnitRange, a::AbstractVector) (r.start > 0 && r.stop <= length(a)) || error("out of bounds") @inbounds for i=length(r):-1:2 @@ -58,8 +72,12 @@ function range_shuffle!(r::UnitRange, a::AbstractVector) end end -"""Return the most frequency label.""" -function vote!(g::AbstractGraph, m::Vector{Int}, c::NeighComm, u::Int) +""" + vote!(g, m, c, u) + +Return the label with greatest frequency. +""" +function vote!(g::AbstractGraph, m::Vector, c::NeighComm, u::Integer) @inbounds for i=1:c.neigh_last-1 c.neigh_cnt[c.neigh_pos[i]] = -1 end @@ -77,19 +95,19 @@ function vote!(g::AbstractGraph, m::Vector{Int}, c::NeighComm, u::Int) end c.neigh_cnt[neigh_comm] += 1 if c.neigh_cnt[neigh_comm] > max_cnt - max_cnt = c.neigh_cnt[neigh_comm] + max_cnt = c.neigh_cnt[neigh_comm] end end # ties breaking randomly range_shuffle!(1:c.neigh_last-1, c.neigh_pos) for lbl in c.neigh_pos - if c.neigh_cnt[lbl] == max_cnt - return lbl - end + if c.neigh_cnt[lbl] == max_cnt + return lbl + end end end -function renumber_labels!(membership::Vector{Int}, label_counters::Vector{Int}) +function renumber_labels!(membership::Vector, label_counters::Vector{Int}) N = length(membership) (maximum(membership) > N || minimum(membership) < 1) && error("Label must between 1 and |V|") j = 1 diff --git a/src/community/modularity.jl b/src/community/modularity.jl index 1c6a9ed86..b14d13046 100644 --- a/src/community/modularity.jl +++ b/src/community/modularity.jl @@ -1,11 +1,11 @@ """ modularity(g, c) -Computes Newman's modularity `Q` -for graph `g` given the partitioning `c`. +Return a value representing Newman's modularity `Q` for the undirected graph +`g` given the partitioning vector `c`. """ -function modularity(g::Graph, c) - n = nv(g) +function modularity end +@traitfn function modularity(g::::(!IsDirected), c::Vector) m = 2*ne(g) m == 0 && return 0. nc = maximum(c) diff --git a/src/connectivity.jl b/src/connectivity.jl index aff0449d8..cc703db7a 100644 --- a/src/connectivity.jl +++ b/src/connectivity.jl @@ -1,20 +1,13 @@ # Parts of this code were taken / derived from Graphs.jl. See LICENSE for # licensing details. - - """ - connected_components!(label::Vector{Int}, g::AbstractGraph) - -Fills `label` with the `id` of the connected component to which it belongs. + connected_components!(label, g) -Arguments: - label: a place to store the output - g: the graph -Output: - c = labels[i] => vertex i belongs to component c. - c is the smallest vertex id in the component. +Fill `label` with the `id` of the connected component in `g` to which it belongs. +Return a vector representing the component assigned to each vertex. The component +value is the smallest vertex ID in the component. """ -function connected_components!(label::Vector{Int}, g::AbstractGraph) +function connected_components!(label::Vector{T}, g::AbstractGraph) where T<:Integer # this version of connected components uses Breadth First Traversal # with custom visitor type in order to improve performance. # one BFS is performed for each component. @@ -23,12 +16,12 @@ function connected_components!(label::Vector{Int}, g::AbstractGraph) # the return type is a vector of labels which can be used directly or # passed to components(a) nvg = nv(g) - visitor = LightGraphs.ComponentVisitorVector(label, 0) + visitor = LightGraphs.ComponentVisitorVector(label, zero(T)) colormap = fill(0, nvg) - queue = Vector{Int}() + queue = Vector{T}() sizehint!(queue, nvg) - for v in 1:nvg - if label[v] == 0 + for v in vertices(g) + if label[v] == zero(T) visitor.labels[v] = v visitor.seed = v traverse_graph!(g, BreadthFirst(), v, visitor; vertexcolormap=colormap, queue=queue) @@ -37,17 +30,17 @@ function connected_components!(label::Vector{Int}, g::AbstractGraph) return label end -"""components_dict(labels) converts an array of labels to a Dict{Int,Vector{Int}} of components +""" + components_dict(labels) -Arguments: - c = labels[i] => vertex i belongs to component c. -Output: - vs = d[c] => vertices in vs belong to component c. +Convert an array of labels to a map of component id to vertices, and return +a map with each key corresponding to a given component id +and each value containing the vertices associated with that component. """ -function components_dict(labels::Vector{Int}) - d = Dict{Int,Vector{Int}}() +function components_dict(labels::Vector{T}) where T<:Integer + d = Dict{T,Vector{T}}() for (v,l) in enumerate(labels) - vec = get(d, l, Vector{Int}()) + vec = get(d, l, Vector{T}()) push!(vec, v) d[l] = vec end @@ -55,20 +48,15 @@ function components_dict(labels::Vector{Int}) end """ - components(labels::Vector{Int}) + components(labels) -Converts an array of labels to a Vector{Vector{Int}} of components - -Arguments: - c = labels[i] => vertex i belongs to component c. -Output: - vs = c[i] => vertices in vs belong to component i. - a = d[i] => if labels[v]==i then v in c[a] end +Given a vector of component labels, return a vector of vectors representing the vertices associated +with a given component id. """ -function components(labels::Vector{Int}) - d = Dict{Int, Int}() - c = Vector{Vector{Int}}() - i = 1 +function components(labels::Vector{T}) where T<:Integer + d = Dict{T, T}() + c = Vector{Vector{T}}() + i = one(T) for (v,l) in enumerate(labels) index = get!(d, l, i) if length(c) >= index @@ -84,12 +72,13 @@ end """ connected_components(g) -Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory)) -of `g` as a vector of components, each represented by a -vector of vertices belonging to the component. +Return the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory)) +of `g` as a vector of components, with each element a vector of vertices +belonging to the component. """ function connected_components(g::AbstractGraph) - label = zeros(Int, nv(g)) + T = eltype(g) + label = zeros(T, nv(g)) connected_components!(label, g) c, d = components(label) return c @@ -98,33 +87,44 @@ end """ is_connected(g) -Returns `true` if `g` is connected. -For DiGraphs, this is equivalent to a test of weak connectivity. +Return `true` if `g` is connected. For directed graphs, this is equivalent to +a test of weak connectivity. +""" +function is_connected end +@traitfn is_connected(g::::(!IsDirected)) = ne(g)+1 >= nv(g) && length(connected_components(g)) == 1 +@traitfn is_connected(g::::IsDirected) = ne(g)+1 >= nv(g) && is_weakly_connected(g) + +""" + weakly_connected_components(g) +Return the weakly connected components of the directed graph `g`. This +is equivalent to the connected components of the undirected equivalent of `g`. """ -is_connected(g::Graph) = ne(g)+1 >= nv(g) && length(connected_components(g)) == 1 -is_connected(g::DiGraph) = ne(g)+1 >= nv(g) && is_weakly_connected(g) +function weakly_connected_components end +@traitfn weakly_connected_components(g::::IsDirected) = connected_components(Graph(g)) -"""Returns connected components of the undirected graph of `g`.""" -weakly_connected_components(g::DiGraph) = connected_components(Graph(g)) +""" + is_weakly_connected(g) -"""Returns `true` if the undirected graph of `g` is connected.""" -is_weakly_connected(g::DiGraph) = length(weakly_connected_components(g)) == 1 +Return `true` if the directed graph `g` is connected. +""" +function is_weakly_connected end +@traitfn is_weakly_connected(g::::IsDirected) = length(weakly_connected_components(g)) == 1 # Adapated from Graphs.jl -type TarjanVisitor <: AbstractGraphVisitor - stack::Vector{Int} +mutable struct TarjanVisitor{T<:Integer} <: AbstractGraphVisitor + stack::Vector{T} onstack::BitVector - lowlink::Vector{Int} - index::Vector{Int} - components::Vector{Vector{Int}} + lowlink::Vector{T} + index::Vector{T} + components::Vector{Vector{T}} end -TarjanVisitor(n::Int) = TarjanVisitor( - Vector{Int}(), +TarjanVisitor(n::T) where T<:Integer = TarjanVisitor( + Vector{T}(), falses(n), - Vector{Int}(), - zeros(Int, n), - Vector{Vector{Int}}() + Vector{T}(), + zeros(T, n), + Vector{Vector{T}}() ) function discover_vertex!(vis::TarjanVisitor, v) @@ -154,11 +154,17 @@ function close_vertex!(vis::TarjanVisitor, v) return true end -"""Computes the (strongly) connected components of a directed graph.""" -function strongly_connected_components(g::DiGraph) +""" + strongly_connected_components(g) + +Compute the strongly connected components of a directed graph `g`. +""" +function strongly_connected_components end +@traitfn function strongly_connected_components(g::::IsDirected) + T = eltype(g) nvg = nv(g) cmap = zeros(Int, nvg) - components = Vector{Vector{Int}}() + components = Vector{Vector{T}}() for v in vertices(g) if cmap[v] == 0 # 0 means not visited yet @@ -172,11 +178,23 @@ function strongly_connected_components(g::DiGraph) return components end -"""Returns `true` if `g` is (strongly) connected.""" -is_strongly_connected(g::DiGraph) = length(strongly_connected_components(g)) == 1 +""" + is_strongly_connected(g) + +Return `true` if directed graph `g` is strongly connected. +""" +function is_strongly_connected end +@traitfn is_strongly_connected(g::::IsDirected) = length(strongly_connected_components(g)) == 1 + +""" + period(g) -"""Computes the (common) period for all nodes in a strongly connected graph.""" -function period(g::DiGraph) +Return the (common) period for all vertices in a strongly connected directed graph. +Will throw an error if the graph is not strongly connected. +""" +function period end +@traitfn function period(g::::IsDirected) + T = eltype(g) !is_strongly_connected(g) && error("Graph must be strongly connected") # First check if there's a self loop @@ -185,7 +203,7 @@ function period(g::DiGraph) g_bfs_tree = bfs_tree(g,1) levels = gdistances(g_bfs_tree,1) tree_diff = difference(g,g_bfs_tree) - edge_values = Vector{Int}() + edge_values = Vector{T}() divisor = 0 for e in edges(tree_diff) @@ -197,11 +215,16 @@ function period(g::DiGraph) return divisor end -"""Computes the condensation graph of the strongly connected components.""" -function condensation(g::DiGraph, scc::Vector{Vector{Int}}) - h = DiGraph(length(scc)) +""" + condensation(g, scc) +Return the condensation graph of the strongly connected components `scc` +in graph `g`. +""" +function condensation end +@traitfn function condensation{T<:Integer}(g::::IsDirected, scc::Vector{Vector{T}}) + h = DiGraph{T}(length(scc)) - component = Vector{Int}(nv(g)) + component = Vector{T}(nv(g)) for (i,s) in enumerate(scc) @inbounds component[s] = i @@ -216,22 +239,35 @@ function condensation(g::DiGraph, scc::Vector{Vector{Int}}) return h end -"""Returns the condensation graph associated with `g`. The condensation `h` of -a graph `g` is the directed graph where every node in `h` represents a strongly -connected component in `g`, and the presence of an edge between between nodes -in `h` indicates that there is at least one edge between the associated -strongly connected components in `g`. The node numbering in `h` corresponds to -the ordering of the components output from `strongly_connected_components`.""" -condensation(g::DiGraph) = condensation(g,strongly_connected_components(g)) - -"""Returns a vector of vectors of integers representing lists of attracting -components in `g`. The attracting components are a subset of the strongly -connected components in which the components do not have any leaving edges.""" -function attracting_components(g::DiGraph) +""" + attracting_components(g) + +Return the condensation graph associated with `g`. + +The condensation `h` of a graph `g` is the directed graph where every node +in `h` represents a strongly connected component in `g`, and the presence +of an edge between between vertices in `h` indicates that there is at least one +edge between the associated strongly connected components in `g`. The node +numbering in `h` corresponds to the ordering of the components output from +`strongly_connected_components`. +""" +condensation(g) = condensation(g,strongly_connected_components(g)) + +""" + attracting_components(g) +Return a vector of vectors of integers representing lists of attracting +components in `g`. + +The attracting components are a subset of the strongly +connected components in which the components do not have any leaving edges. +""" +function attracting_components end +@traitfn function attracting_components(g::::IsDirected) + T = eltype(g) scc = strongly_connected_components(g) cond = condensation(g,scc) - attracting = Vector{Int}() + attracting = Vector{T}() for v in vertices(cond) if outdegree(cond,v) == 0 @@ -241,14 +277,14 @@ function attracting_components(g::DiGraph) return scc[attracting] end -type NeighborhoodVisitor <: AbstractGraphVisitor - d::Int - neigs::Vector{Int} +mutable struct NeighborhoodVisitor{T<:Integer} <: AbstractGraphVisitor + d::T + neigs::Vector{T} end -NeighborhoodVisitor(d::Int) = NeighborhoodVisitor(d, Vector{Int}()) +NeighborhoodVisitor(d::T) where T<:Integer = NeighborhoodVisitor(d, Vector{T}()) -function examine_neighbor!(visitor::NeighborhoodVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(visitor::NeighborhoodVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) -ucolor > visitor.d && return false # color is negative for not-closed vertices if vcolor == 0 push!(visitor.neigs, v) @@ -258,28 +294,33 @@ end """ - neighborhood(g, v::Int, d::Int; dir=:out) + neighborhood(g, v, d) + +Return a vector of the vertices in `g` at a geodesic distance less or equal to `d` +from `v`. -Returns a vector of the vertices in `g` at distance less or equal to `d` -from `v`. If `g` is a `DiGraph` the `dir` optional argument specifies the edge direction -the edge direction with respect to `v` (i.e. `:in` or `:out`) to be considered. +### Optional Arguments +- `dir=:out`: If `g` is directed, this argument specifies the edge direction +with respect to `v` of the edges to be considered. Possible values: `:in` or `:out`. """ -function neighborhood(g::AbstractGraph, v::Int, d::Int; dir=:out) +function neighborhood(g::AbstractGraph, v::Integer, d::Integer; dir=:out) @assert d >= 0 "Distance has to be greater then zero." + T = eltype(g) visitor = NeighborhoodVisitor(d) - push!(visitor.neigs, v) + push!(visitor.neigs, T(v)) traverse_graph!(g, BreadthFirst(), v, visitor, - vertexcolormap=Dict{Int,Int}(), dir=dir) + vertexcolormap=Dict{T,Int}(), dir=dir) return visitor.neigs end """ - isgraphical(degs::Vector{Int}) + isgraphical(degs) -Check whether the degree sequence `degs` is graphical, according to +Return true if the degree sequence `degs` is graphical, according to [Erdös-Gallai condition](http://mathworld.wolfram.com/GraphicSequence.html). -Time complexity: O(length(degs)^2) +### Performance + Time complexity: ``\\mathcal{O}(|degs|^2)`` """ function isgraphical(degs::Vector{Int}) iseven(sum(degs)) || return false diff --git a/src/core.jl b/src/core.jl index 70d6b167e..a22ecc297 100644 --- a/src/core.jl +++ b/src/core.jl @@ -1,229 +1,101 @@ -abstract AbstractPathState +abstract type AbstractPathState end # modified from http://stackoverflow.com/questions/25678112/insert-item-into-a-sorted-list-with-julia-with-and-without-duplicates # returns true if insert succeeded, false if it was a duplicate -_insert_and_dedup!(v::Vector{Int}, x::Int) = isempty(splice!(v, searchsorted(v,x), x)) +_insert_and_dedup!{T<:Integer}(v::Vector{T}, x::T) = isempty(splice!(v, searchsorted(v,x), x)) -"""A type representing a single edge between two vertices of a graph.""" -abstract AbstractEdge -immutable Edge <: AbstractEdge - src::Int - dst::Int -end - -show(io::IO, e::Edge) = print(io, "Edge $(e.src) => $(e.dst)") - -"""Return source of an edge.""" -src(e::Edge) = e.src -"""Return destination of an edge.""" -dst(e::Edge) = e.dst - -convert(::Type{Pair}, e::Edge) = Pair(src(e), dst(e)) -convert(::Type{Tuple}, e::Edge) = (src(e), dst(e)) - -reverse(e::Edge) = Edge(dst(e), src(e)) -is_ordered(e::Edge) = src(e) <= dst(e) - -==(e1::Edge, e2::Edge) = (src(e1) == src(e2) && dst(e1) == dst(e2)) - -"""An abstract type representing a graph.""" -abstract AbstractGraph - -"""A type representing an undirected graph.""" -type Graph <: AbstractGraph - vertices::UnitRange{Int} - ne::Int - fadjlist::Vector{Vector{Int}} # [src]: (dst, dst, dst) -end - -"""A type representing a directed graph.""" -type DiGraph <: AbstractGraph - vertices::UnitRange{Int} - ne::Int - fadjlist::Vector{Vector{Int}} # [src]: (dst, dst, dst) - badjlist::Vector{Vector{Int}} # [dst]: (src, src, src) -end - -"""Return the vertices of a graph.""" -vertices(g::AbstractGraph) = g.vertices - -"""Return an iterator to the edges of a graph. -The returned iterator is valid for one pass over the edges, and is invalidated by changes to `g`. """ -edges(g::AbstractGraph) = EdgeIter(g) - -"""Returns the forward adjacency list of a graph. - -The Array, where each vertex the Array of destinations for each of the edges eminating from that vertex. -This is equivalent to: - - fadj = [Vector{Int}() for _ in vertices(g)] - for e in edges(g) - push!(fadj[src(e)], dst(e)) - end - fadj - -For most graphs types this is pre-calculated. - -The optional second argument take the `v`th vertex adjacency list, that is: - - fadj(g, v::Int) == fadj(g)[v] - -NOTE: returns a reference, not a copy. Do not modify result. + is_ordered(e) +Return true if the source vertex of edge `e` is less than or equal to +the destination vertex. """ -fadj(g::AbstractGraph) = g.fadjlist -fadj(g::AbstractGraph, v::Int) = g.fadjlist[v] - -"""Returns true if all of the vertices and edges of `g` are contained in `h`.""" -function issubset{T<:AbstractGraph}(g::T, h::T) - (gmin, gmax) = extrema(vertices(g)) - (hmin, hmax) = extrema(vertices(h)) - return (hmin <= gmin <= gmax <= hmax) && issubset(edges(g), edges(h)) -end - - -"""Add `n` new vertices to the graph `g`. Returns true if all vertices -were added successfully, false otherwise.""" -function add_vertices!(g::AbstractGraph, n::Integer) - added = true - for i = 1:n - added &= add_vertex!(g) - end - return added -end +is_ordered(e::AbstractEdge) = src(e) <= dst(e) -"""Return true if the graph `g` has an edge from `u` to `v`.""" -has_edge(g::AbstractGraph, u::Int, v::Int) = has_edge(g, Edge(u, v)) +""" + add_vertices!(g, n) +Add `n` new vertices to the graph `g`. +Return `true` if all vertices were added successfully, `false` otherwise. +""" +add_vertices!(g::AbstractGraph, n::Integer) = all([add_vertex!(g) for i=1:n]) """ - in_edges(g, v) + indegree(g[, v]) -Returns an Array of the edges in `g` that arrive at vertex `v`. -`v=dst(e)` for each returned edge `e`. +Return a vector corresponding to the number of edges which end at each vertex in +graph `g`. If `v` is specified, only return degrees for vertices in `v`. """ -in_edges(g::AbstractGraph, v::Int) = [Edge(x,v) for x in badj(g, v)] +indegree(g::AbstractGraph, v::Integer) = length(in_neighbors(g, v)) +indegree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [indegree(g,x) for x in v] """ - out_edges(g, v) + outdegree(g[, v]) -Returns an Array of the edges in `g` that depart from vertex `v`. -`v = src(e)` for each returned edge `e`. +Return a vector corresponding to the number of edges which start at each vertex in +graph `g`. If `v` is specified, only return degrees for vertices in `v`. """ -out_edges(g::AbstractGraph, v::Int) = [Edge(v,x) for x in fadj(g,v)] +outdegree(g::AbstractGraph, v::Integer) = length(out_neighbors(g, v)) +outdegree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [outdegree(g,x) for x in v] +""" + degree(g[, v]) +Return a vector corresponding to the number of edges which start or end at each +vertex in graph `g`. If `v` is specified, only return degrees for vertices in `v`. +For directed graphs, this value equals the incoming plus outgoing edges. +For undirected graphs, it equals the connected edges. +""" +function degree end +@traitfn degree(g::::IsDirected, v::Integer) = indegree(g, v) + outdegree(g, v) +@traitfn degree(g::::(!IsDirected), v::Integer) = indegree(g, v) -"""Return true if `v` is a vertex of `g`.""" -has_vertex(g::AbstractGraph, v::Int) = v in vertices(g) +degree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [degree(g, x) for x in v] """ - nv(g) + Δout(g) -The number of vertices in `g`. +Return the maximum [`outdegree`](@ref) of vertices in `g`. """ -nv(g::AbstractGraph) = length(vertices(g)) +Δout(g) = noallocextreme(outdegree,(>), typemin(Int), g) """ - ne(g) + δout(g) -The number of edges in `g`. +Return the minimum [`outdegree`](@ref) of vertices in `g`. """ -ne(g::AbstractGraph) = g.ne +δout(g) = noallocextreme(outdegree,(<), typemax(Int), g) """ - add_edge!(g, u, v) + Δin(g) -Add a new edge to `g` from `u` to `v`. -Will return false if add fails (e.g., if vertices are not in the graph); true otherwise. +Return the maximum [`indegree`](@ref) of vertices in `g`. """ -add_edge!(g::AbstractGraph, u::Int, v::Int) = add_edge!(g, Edge(u, v)) +Δin(g) = noallocextreme(indegree,(>), typemin(Int), g) """ - rem_edge!(g, u, v) - -Remove the edge from `u` to `v`. + δin(g) -Returns false if edge removal fails (e.g., if edge does not exist); true otherwise. +Return the minimum [`indegree`](ref) of vertices in `g`. """ -rem_edge!(g::AbstractGraph, u::Int, v::Int) = rem_edge!(g, Edge(u, v)) +δin(g) = noallocextreme(indegree,(<), typemax(Int), g) """ - rem_vertex!(g, v) + Δ(g) -Remove the vertex `v` from graph `g`. -This operation has to be performed carefully if one keeps external data structures indexed by -edges or vertices in the graph, since internally the removal is performed swapping the vertices `v` and `n=nv(g)`, -and removing the vertex `n` from the graph. -After removal the vertices in the ` g` will be indexed by 1:n-1. -This is an O(k^2) operation, where `k` is the max of the degrees of vertices `v` and `n`. -Returns false if removal fails (e.g., if vertex is not in the graph); true otherwise. +Return the maximum [`degree`](@ref) of vertices in `g`. """ -function rem_vertex!(g::AbstractGraph, v::Int) - v in vertices(g) || return false - n = nv(g) - - edgs = in_edges(g, v) - for e in edgs - rem_edge!(g, e) - end - neigs = copy(in_neighbors(g, n)) - for i in neigs - rem_edge!(g, Edge(i, n)) - end - if v != n - for i in neigs - add_edge!(g, Edge(i, v)) - end - end - - if is_directed(g) - edgs = out_edges(g, v) - for e in edgs - rem_edge!(g, e) - end - neigs = copy(out_neighbors(g, n)) - for i in neigs - rem_edge!(g, Edge(n, i)) - end - if v != n - for i in neigs - add_edge!(g, Edge(v, i)) - end - end - end - - g.vertices = 1:n-1 - pop!(g.fadjlist) - if is_directed(g) - pop!(g.badjlist) - end - return true -end - -"""Return the number of edges which end at vertex `v`.""" -indegree(g::AbstractGraph, v::Int) = length(badj(g,v)) -"""Return the number of edges which start at vertex `v`.""" -outdegree(g::AbstractGraph, v::Int) = length(fadj(g,v)) +Δ(g) = noallocextreme(degree,(>), typemin(Int), g) +""" + δ(g) +Return the minimum [`degree`](@ref) of vertices in `g`. +""" +δ(g) = noallocextreme(degree,(<), typemax(Int), g) -indegree(g::AbstractGraph, v::AbstractArray{Int,1} = vertices(g)) = [indegree(g,x) for x in v] -outdegree(g::AbstractGraph, v::AbstractArray{Int,1} = vertices(g)) = [outdegree(g,x) for x in v] -degree(g::AbstractGraph, v::AbstractArray{Int,1} = vertices(g)) = [degree(g,x) for x in v] -"Return the maximum `outdegree` of vertices in `g`." -Δout(g) = noallocextreme(outdegree,(>), typemin(Int), g) -"Return the minimum `outdegree` of vertices in `g`." -δout(g) = noallocextreme(outdegree,(<), typemax(Int), g) -"Return the maximum `indegree` of vertices in `g`." -Δin(g) = noallocextreme(indegree,(>), typemin(Int), g) -"Return the minimum `indegree` of vertices in `g`." -δin(g) = noallocextreme(indegree,(<), typemax(Int), g) -"Return the maximum `degree` of vertices in `g`." -Δ(g) = noallocextreme(degree,(>), typemin(Int), g) -"Return the minimum `degree` of vertices in `g`." -δ(g) = noallocextreme(degree,(<), typemax(Int), g) - -"Computes the extreme value of `[f(g,i) for i=i:nv(g)]` without gathering them all" +""" + noallocextreme(f, comparison, initial, g) +Compute the extreme value of `[f(g,i) for i=i:nv(g)]` without gathering them all +""" function noallocextreme(f, comparison, initial, g) value = initial - for i in 1:nv(g) + for i in vertices(g) funci = f(g, i) if comparison(funci, value) value = funci @@ -235,37 +107,88 @@ end """ degree_histogram(g) -Returns a `StatsBase.Histogram` of the degrees of vertices in `g`. +Return a `StatsBase.Histogram` of the degrees of vertices in `g`. """ degree_histogram(g::AbstractGraph) = fit(Histogram, degree(g)) -"""Returns a list of all neighbors connected to vertex `v` by an incoming edge. +""" + neighbors(g, v) + +Return a list of all neighbors reachable from vertex `v` in `g`. +For directed graphs, the default is equivalent to [`out_neighbors`](@ref); +use [`all_neighbors`](@ref) to list inbound and outbound neighbors. + +### Implementation Notes +Returns a reference, not a copy. Do not modify result. +""" +neighbors(g::AbstractGraph, v::Integer) = out_neighbors(g, v) + +""" + all_neighbors(g, v) +Return a list of all inbound and outbound neighbors of `v` in `g`. +For undirected graphs, this is equivalent to both [`out_neighbors`](@ref) +and [`in_neighbors`](@ref). + +### Implementation Notes +Returns a reference, not a copy. Do not modify result. +""" +function all_neighbors end +@traitfn all_neighbors(g::::IsDirected, v::Integer) = + union(out_neighbors(g, v), in_neighbors(g, v)) +@traitfn all_neighbors(g::::(!IsDirected), v::Integer) = + neighbors(g, v) + -NOTE: returns a reference, not a copy. Do not modify result. """ -in_neighbors(g::AbstractGraph, v::Int) = badj(g,v) -"""Returns a list of all neighbors connected to vertex `v` by an outgoing edge. + common_neighbors(g, u, v) -NOTE: returns a reference, not a copy. Do not modify result. +Return the neighbors common to vertices `u` and `v` in `g`. + +### Implementation Notes +Returns a reference, not a copy. Do not modify result. """ -out_neighbors(g::AbstractGraph, v::Int) = fadj(g,v) +common_neighbors(g::AbstractGraph, u::Integer, v::Integer) = + intersect(neighbors(g, u), neighbors(g, v)) -"""Returns a list of all neighbors of vertex `v` in `g`. +""" + has_self_loops(g) -For DiGraphs, this is equivalent to `out_neighbors(g, v)`. +Return true if `g` has any self loops. +""" +has_self_loops(g::AbstractGraph) = nv(g) == 0? false : any(v->has_edge(g, v, v), vertices(g)) -NOTE: returns a reference, not a copy. Do not modify result. """ -neighbors(g::AbstractGraph, v::Int) = out_neighbors(g, v) + num_self_loops(g) -"Returns the neighbors common to vertices `u` and `v` in `g`." -common_neighbors(g::AbstractGraph, u::Int, v::Int) = intersect(neighbors(g,u), neighbors(g,v)) +Return the number of self loops in `g`. +""" +num_self_loops(g::AbstractGraph) = nv(g) == 0 ? 0 : sum(v->has_edge(g, v, v), vertices(g)) -@deprecate has_self_loop has_self_loops +@doc_str """ + density(g) +Return the density of `g`. +Density is defined as the ratio of the number of actual edges to the +number of possible edges (``|V|×(|V|-1)`` for directed graphs and +``\\frac{|V|×(|V|-1)}{2}`` for undirected graphs). +""" +function density end +@traitfn density(g::::IsDirected) = +ne(g) / (nv(g) * (nv(g)-1)) +@traitfn density(g::::(!IsDirected)) = +(2*ne(g)) / (nv(g) * (nv(g)-1)) -"Returns true if `g` has any self loops." -has_self_loops(g::AbstractGraph) = any(v->has_edge(g, v, v), vertices(g)) +""" + squash(g) -"Returns the number of self loops in `g`." -num_self_loops(g::AbstractGraph) = sum(v->has_edge(g, v, v), vertices(g)) +Return a copy of a graph with the smallest practical type that +can accommodate all vertices. +""" +function squash(g::AbstractGraph) + gtype = is_directed(g)? DiGraph : Graph + validtypes = [UInt8, UInt16, UInt32, UInt64, Int] + nvg = nv(g) + for T in validtypes + nvg < typemax(T) && return gtype{T}(g) + end +end diff --git a/src/deprecations.jl b/src/deprecations.jl new file mode 100644 index 000000000..709328273 --- /dev/null +++ b/src/deprecations.jl @@ -0,0 +1,2 @@ +@deprecate in_edges in_neighbors +@deprecate out_edges out_neighbors diff --git a/src/digraph-transitivity.jl b/src/digraph-transitivity.jl index daf48d62c..bcea8fb73 100644 --- a/src/digraph-transitivity.jl +++ b/src/digraph-transitivity.jl @@ -1,51 +1,44 @@ -""" -```transitiveclosure!(dg::DiGraph, selflooped = false)``` +@doc_str """ + transitiveclosure!(g, selflooped=false) Compute the transitive closure of a directed graph, using the Floyd-Warshall -algorithm. - -Version of the function that modifies the original graph. +algorithm. If `selflooped` is true, add self loops to the graph. -Note: This is an O(V^3) algorithm. +### Performance +Time complexity is \\mathcal{O}(|V|^3). -# Arguments -* `dg`: the directed graph on which the transitive closure is computed. -* `selflooped`: whether self loop should be added to the directed graph, -default to `false`. +### Implementation Notes +This version of the function modifies the original graph. """ -function transitiveclosure!(dg::DiGraph, selflooped = false) - for k in vertices(dg) - for i in vertices(dg) +function transitiveclosure! end +@traitfn function transitiveclosure!(g::::IsDirected, selflooped=false) + for k in vertices(g) + for i in vertices(g) i == k && continue - for j in vertices(dg) + for j in vertices(g) j == k && continue - if (has_edge(dg, i, k) && has_edge(dg, k, j)) + if (has_edge(g, i, k) && has_edge(g, k, j)) if ( i != j || selflooped ) - add_edge!(dg, i, j) + add_edge!(g, i, j) end end end end end - return dg + return g end """ -```transitiveclosure(dg::DiGraph, selflooped = false)``` + transitiveclosure(g, selflooped=false) Compute the transitive closure of a directed graph, using the Floyd-Warshall -algorithm. - -Version of the function that does not modify the original graph. - -Note: This is an O(V^3) algorithm. +algorithm. Return a graph representing the transitive closure. If `selflooped` +is `true`, add self loops to the graph. -# Arguments -* `dg`: the directed graph on which the transitive closure is computed. -* `selflooped`: whether self loop should be added to the directed graph, -default to `false`. +### Performance +Time complexity is \\mathcal{O}(|V|^3). """ -function transitiveclosure(dg::DiGraph, selflooped = false) - copydg = copy(dg) - return transitiveclosure!(copydg, selflooped) +function transitiveclosure(g::DiGraph, selflooped = false) + copyg = copy(g) + return transitiveclosure!(copyg, selflooped) end diff --git a/src/digraph.jl b/src/digraph.jl deleted file mode 100644 index 8b19a2e06..000000000 --- a/src/digraph.jl +++ /dev/null @@ -1,119 +0,0 @@ -function show(io::IO, g::DiGraph) - if nv(g) == 0 - print(io, "empty directed graph") - else - print(io, "{$(nv(g)), $(ne(g))} directed graph") - end -end - -function DiGraph(n::Int) - fadjlist = Vector{Vector{Int}}() - badjlist = Vector{Vector{Int}}() - for i = 1:n - push!(badjlist, Vector{Int}()) - push!(fadjlist, Vector{Int}()) - end - return DiGraph(1:n, 0, badjlist, fadjlist) -end - -DiGraph() = DiGraph(0) - -function DiGraph{T<:Real}(adjmx::SparseMatrixCSC{T}) - dima, dimb = size(adjmx) - isequal(dima,dimb) || error("Adjacency / distance matrices must be square") - - g = DiGraph(dima) - maxc = length(adjmx.colptr) - for c = 1:(maxc-1) - for rind = adjmx.colptr[c]:adjmx.colptr[c+1]-1 - isnz = (adjmx.nzval[rind] != zero(T)) - if isnz - r = adjmx.rowval[rind] - add_edge!(g,r,c) - end - end - end - return g -end - -function DiGraph{T<:Real}(adjmx::AbstractMatrix{T}) - dima,dimb = size(adjmx) - isequal(dima,dimb) || error("Adjacency / distance matrices must be square") - - g = DiGraph(dima) - for i in find(adjmx) - ind = ind2sub((dima,dimb),i) - add_edge!(g,ind...) - end - return g -end - -function DiGraph(g::Graph) - h = DiGraph(nv(g)) - h.ne = ne(g) * 2 - num_self_loops(g) - h.fadjlist = deepcopy(fadj(g)) - h.badjlist = deepcopy(badj(g)) - return h -end - -badj(g::DiGraph) = g.badjlist -badj(g::DiGraph, v::Int) = badj(g)[v] - - -function copy(g::DiGraph) - return DiGraph(g.vertices, g.ne, deepcopy(g.fadjlist), deepcopy(g.badjlist)) -end - -==(g::DiGraph, h::DiGraph) = - vertices(g) == vertices(h) && - ne(g) == ne(h) && - fadj(g) == fadj(h) && - badj(g) == badj(h) - -is_directed(g::DiGraph) = true - -function add_edge!(g::DiGraph, e::Edge) - s, d = Tuple(e) - (s in vertices(g) && d in vertices(g)) || return false - inserted = _insert_and_dedup!(g.fadjlist[s], d) - if inserted - g.ne += 1 - end - return inserted && _insert_and_dedup!(g.badjlist[d], s) -end - - -function rem_edge!(g::DiGraph, e::Edge) - has_edge(g,e) || return false - i = searchsorted(g.fadjlist[src(e)], dst(e))[1] - deleteat!(g.fadjlist[src(e)], i) - i = searchsorted(g.badjlist[dst(e)], src(e))[1] - deleteat!(g.badjlist[dst(e)], i) - g.ne -= 1 - return true -end - - -function add_vertex!(g::DiGraph) - g.vertices = 1:nv(g)+1 - push!(g.badjlist, Vector{Int}()) - push!(g.fadjlist, Vector{Int}()) - - return true -end - - -function has_edge(g::DiGraph, e::Edge) - u, v = Tuple(e) - u > nv(g) || v > nv(g) && return false - if degree(g,u) < degree(g,v) - return length(searchsorted(fadj(g,u), v)) > 0 - else - return length(searchsorted(badj(g,v), u)) > 0 - end -end - -degree(g::DiGraph, v::Int) = indegree(g,v) + outdegree(g,v) -"Returns all the vertices which share an edge with `v`." -all_neighbors(g::DiGraph, v::Int) = union(in_neighbors(g,v), out_neighbors(g,v)) -density(g::DiGraph) = ne(g) / (nv(g) * (nv(g)-1)) diff --git a/src/distance.jl b/src/distance.jl index 19c8fb6c2..95c12ea46 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -1,88 +1,112 @@ # used in shortest path calculations -# has_distances{T}(distmx::AbstractArray{T,2}) = -# issparse(distmx)? (nnz(distmx) > 0) : !isempty(distmx) -type DefaultDistance<:AbstractArray{Int, 2} +struct DefaultDistance<:AbstractMatrix{Int} nv::Int DefaultDistance(nv::Int=typemax(Int)) = new(nv) end -getindex(::DefaultDistance, s::Int, d::Int) = 1 +getindex(::DefaultDistance, s::Integer, d::Integer) = 1 getindex(::DefaultDistance, s::UnitRange, d::UnitRange) = DefaultDistance(length(s)) size(d::DefaultDistance) = (d.nv, d.nv) transpose(d::DefaultDistance) = d ctranspose(d::DefaultDistance) = d """ -Calculates the eccentricity[ies] of a vertex `v`, vertex vector `vs`, or the -entire graph. An optional matrix of edge distances may be supplied. + eccentricity(g[, v][, distmx]) + +Return the eccentricity[ies] of a vertex / vertex list `v` or the +entire graph. An optional matrix of edge distances may be supplied; if missing, +edge distances default to `1`. The eccentricity of a vertex is the maximum shortest-path distance between it and all other vertices in the graph. -Because this function must calculate shortest paths for all vertices supplied -in the argument list, it may take a long time. - The output is either a single float (when a single vertex is provided) or a vector of floats corresponding to the vertex vector. If no vertex vector is provided, the vector returned corresponds to each vertex in the graph. -Note: the eccentricity vector returned by `eccentricity()` may be used as input +### Performance +Because this function must calculate shortest paths for all vertices supplied +in the argument list, it may take a long time. + +### Implementation Notes +The eccentricity vector returned by `eccentricity()` may be used as input for the rest of the distance measures below. If an eccentricity vector is provided, it will be used. Otherwise, an eccentricity vector will be calculated for each call to the function. It may therefore be more efficient to calculate, store, and pass the eccentricities if multiple distance measures are desired. """ -function eccentricity{T}( +function eccentricity( g::AbstractGraph, - v::Int, - distmx::AbstractArray{T, 2} = DefaultDistance() -) + v::Integer, + distmx::AbstractMatrix{T} = DefaultDistance() +) where T e = maximum(dijkstra_shortest_paths(g,v,distmx).dists) e == typemax(T) && error("Infinite path length detected") return e end -eccentricity{T}( +eccentricity( g::AbstractGraph, - vs::AbstractArray{Int, 1}=vertices(g), - distmx::AbstractArray{T, 2} = DefaultDistance() -) = - [eccentricity(g,v,distmx) for v in vs] + vs::AbstractVector = vertices(g), + distmx::AbstractMatrix = DefaultDistance() +) = [eccentricity(g,v,distmx) for v in vs] -eccentricity{T}(g::AbstractGraph, distmx::AbstractArray{T, 2}) = +eccentricity(g::AbstractGraph, distmx::AbstractMatrix) = eccentricity(g, vertices(g), distmx) -"""Returns the maximum eccentricity of the graph.""" -diameter{T}(all_e::Vector{T}) = maximum(all_e) -diameter{T}(g::AbstractGraph, distmx::AbstractArray{T, 2} = DefaultDistance()) = +""" + diameter(g, distmx=DefaultDistance()) + diameter(eccentricities) + +Given a graph and optional distance matrix, or a vector of precomputed +eccentricities, return the maximum eccentricity of the graph. +""" +diameter(eccentricities::Vector) = maximum(eccentricities) +diameter(g::AbstractGraph, distmx::AbstractMatrix = DefaultDistance())= maximum(eccentricity(g, distmx)) -"""Returns the set of all vertices whose eccentricity is equal to the graph's -diameter (that is, the set of vertices with the largest eccentricity). """ -function periphery{T}(all_e::Vector{T}) + periphery(g, distmx=DefaultDistance()) + periphery(eccentricities) - diam = maximum(all_e) - return filter((x)->all_e[x] == diam, 1:length(all_e)) +Given a graph and optional distance matrix, or a vector of precomputed +eccentricities, return the set of all vertices whose eccentricity is +equal to the graph's diameter (that is, the set of vertices with the +largest eccentricity). +""" +function periphery(eccentricities::Vector) + diam = maximum(eccentricities) + return filter((x)->eccentricities[x] == diam, 1:length(eccentricities)) end -periphery{T}(g::AbstractGraph, distmx::AbstractArray{T, 2} = DefaultDistance()) = +periphery(g::AbstractGraph, distmx::AbstractMatrix = DefaultDistance()) = periphery(eccentricity(g, distmx)) -"""Returns the minimum eccentricity of the graph.""" -radius{T}(all_e::Vector{T}) = minimum(all_e) -radius{T}(g::AbstractGraph, distmx::AbstractArray{T, 2} = DefaultDistance()) = +""" + radius(g, distmx=DefaultDistance()) + radius(eccentricities) + +Given a graph and optional distance matrix, or a vector of precomputed +eccentricities, return the minimum eccentricity of the graph. +""" +radius(eccentricities::Vector) = minimum(eccentricities) +radius(g::AbstractGraph, distmx::AbstractMatrix = DefaultDistance()) = minimum(eccentricity(g, distmx)) -"""Returns the set of all vertices whose eccentricity is equal to the graph's -radius (that is, the set of vertices with the smallest eccentricity). """ -function center{T}(all_e::Vector{T}) - rad = radius(all_e) - return filter((x)->all_e[x] == rad, 1:length(all_e)) + center(g, distmx=DefaultDistance()) + center(eccentricities) + +Given a graph and optional distance matrix, or a vector of precomputed +eccentricities, return the set of all vertices whose eccentricity is equal +to the graph's radius (that is, the set of vertices with the smallest eccentricity). +""" +function center(eccentricities::Vector) + rad = radius(eccentricities) + return filter((x)->eccentricities[x] == rad, 1:length(eccentricities)) end -center{T}(g::AbstractGraph, distmx::AbstractArray{T, 2} = DefaultDistance()) = +center(g::AbstractGraph, distmx::AbstractMatrix = DefaultDistance()) = center(eccentricity(g, distmx)) diff --git a/src/edgeiter.jl b/src/edgeiter.jl deleted file mode 100644 index 81334d1ad..000000000 --- a/src/edgeiter.jl +++ /dev/null @@ -1,76 +0,0 @@ -immutable EdgeIterState - s::Int # src vertex - di::Int # index into adj of dest vertex - fin::Bool -end - -type EdgeIter - m::Int - adj::Vector{Vector{Int}} - directed::Bool -end - -eltype(::Type{EdgeIter}) = Edge - -EdgeIter(g::Graph) = EdgeIter(ne(g), g.fadjlist, false) -EdgeIter(g::DiGraph) = EdgeIter(ne(g), g.fadjlist, true) - -function _next(eit::EdgeIter, state::EdgeIterState = EdgeIterState(1,1,false), first::Bool = true) - s = state.s - di = state.di - if !first - di += 1 - end - fin = state.fin - while s <= length(eit.adj) - arr = eit.adj[s] - while di <= length(arr) - if eit.directed || s <= arr[di] - return EdgeIterState(s, di, fin) - end - di += 1 - end - s += 1 - di = 1 - end - fin = true - return EdgeIterState(s, di, fin) -end - -start(eit::EdgeIter) = _next(eit) -done(eit::EdgeIter, state::EdgeIterState) = state.fin -length(eit::EdgeIter) = eit.m - -function next(eit::EdgeIter, state) - edge = Edge(state.s, eit.adj[state.s][state.di]) - return(edge, _next(eit, state, false)) -end - -function _isequal(e1::EdgeIter, e2) - for e in e2 - s, d = Tuple(e) - found = length(searchsorted(e1.adj[s], d)) > 0 - if !e1.directed - found = found || length(searchsorted(e1.adj[d],s)) > 0 - end - !found && return false - end - return true -end -==(e1::EdgeIter, e2::AbstractArray{Edge,1}) = _isequal(e1, e2) -==(e1::AbstractArray{Edge,1}, e2::EdgeIter) = _isequal(e2, e1) -==(e1::EdgeIter, e2::Set{Edge}) = _isequal(e1, e2) -==(e1::Set{Edge}, e2::EdgeIter) = _isequal(e2, e1) - - -function ==(e1::EdgeIter, e2::EdgeIter) - length(e1.adj) == length(e2.adj) || return false - e1.directed == e2.directed || return false - for i in 1:length(e1.adj) - e1.adj[i] == e2.adj[i] || return false - end - return true -end - -show(io::IO, eit::EdgeIter) = write(io, "EdgeIter $(eit.m)") -show(io::IO, s::EdgeIterState) = write(io, "EdgeIterState [$(s.s), $(s.di), $(s.fin)]") diff --git a/src/edit_distance.jl b/src/edit_distance.jl index 102abdffd..d20746048 100644 --- a/src/edit_distance.jl +++ b/src/edit_distance.jl @@ -1,14 +1,20 @@ -""" -Computes the edit distance between graphs G₁ and G₂. +@doc_str """ + edit_distance(G₁::AbstractGraph, G₂::AbstractGraph) + +Compute the edit distance between graphs `G₁` and `G₂`. Return the minimum +edit cost and edit path to transform graph `G₁` into graph `G₂``. +An edit path consists of a sequence of pairs of vertices ``(u,v) ∈ [0,|G₁|] × [0,|G₂|]`` +representing vertex operations: + +- ``(0,v)``: insertion of vertex ``v ∈ G₂`` +- ``(u,0)``: deletion of vertex ``u ∈ G₁`` +- ``(u>0,v>0)``: substitution of vertex ``u ∈ G₁`` by vertex ``v ∈ G₂`` -Returns the minimum edit cost and edit path to transform graph -G₁ into graph G₂. An edit path consists of a sequence of pairs -of vertices (u,v) ∈ [0,|G₁|] × [0,|G₂|] representing vertex -operations: -- (0,v): insertion of vertex v ∈ G₂ -- (u,0): deletion of vertex u ∈ G₁ -- (u>0,v>0): substitution of vertex u ∈ G₁ by vertex v ∈ G₂ +### Optional Arguments +- `insert_cost::Function=v->1.0` +- `delete_cost::Function=u->1.0` +- `subst_cost::Function=(u,v)->0.5` By default, the algorithm uses constant operation costs. The user can provide classical Minkowski costs computed from vertex @@ -18,24 +24,21 @@ search, for example: ``` edit_distance(G₁, G₂, subst_cost=MinkowskiCost(μ₁, μ₂)) ``` +- `heuristic::Function=DefaultEditHeuristic`: a custom heuristic provided to the A* +search in case the default heuristic is not satisfactory. -A custom heuristic can be provided to the A* search in case the -default heuristic is not satisfactory. - -Performance tips: ------------------ -- Given two graphs |G₁| < |G₂|, `edit_distance(G₁, G₂)` is faster to +### Performance +- Given two graphs ``|G₁| < |G₂|``, `edit_distance(G₁, G₂)` is faster to compute than `edit_distance(G₂, G₁)`. Consider swapping the arguments -if involved costs are ``symmetric''. +if involved costs are equivalent. - The use of simple Minkowski costs can improve performance considerably. - Exploit vertex attributes when designing operation costs. -For further details, please refer to: - -RIESEN, K., 2015. Structural Pattern Recognition with Graph Edit -Distance: Approximation Algorithms and Applications. (Chapter 2) +### References +- RIESEN, K., 2015. Structural Pattern Recognition with Graph Edit Distance: Approximation Algorithms and Applications. (Chapter 2) -Author: Júlio Hoffimann Mendes (juliohm@stanford.edu) +### Author +- Júlio Hoffimann Mendes (juliohm@stanford.edu) """ function edit_distance(G₁::AbstractGraph, G₂::AbstractGraph; insert_cost::Function=v->1.0, @@ -100,21 +103,33 @@ function DefaultEditHeuristic(λ, G₁::AbstractGraph, G₂::AbstractGraph) return nv(G₂) - length(vs) end + #------------------------- # Edit path cost functions #------------------------- """ + MinkowskiCost(μ₁, μ₂; p::Real=1) + For labels μ₁ on the vertices of graph G₁ and labels μ₂ on the vertices of graph G₂, compute the p-norm cost of substituting vertex u ∈ G₁ by vertex v ∈ G₂. + +### Optional Arguments +`p=1`: the p value for p-norm calculation. """ function MinkowskiCost(μ₁::AbstractVector, μ₂::AbstractVector; p::Real=1) (u,v) -> norm(μ₁[u] - μ₂[v], p) end """ -Similar to MinkowskiCost, but ensures costs smaller than 2τ. + BoundedMinkowskiCost(μ₁, μ₂) + +Return value similar to `MinkowskiCost`, but ensure costs smaller than 2τ. + +### Optional Arguments +`p=1`: the p value for p-norm calculation. +`τ=1`: value specifying half of the upper limit of the Minkowski cost. """ function BoundedMinkowskiCost(μ₁::AbstractVector, μ₂::AbstractVector; p::Real=1, τ::Real=1) (u,v) -> 1 / (1/(2τ) + exp(-norm(μ₁[u] - μ₂[v], p))) diff --git a/src/flow/boykov_kolmogorov.jl b/src/flow/boykov_kolmogorov.jl index 69e2fea3a..c42772758 100644 --- a/src/flow/boykov_kolmogorov.jl +++ b/src/flow/boykov_kolmogorov.jl @@ -1,208 +1,205 @@ """ -Computes the max-flow/min-cut between source and target using -Boykov-Kolmogorov algorithm. + boykov_kolmogorov_impl(residual_graph, source, target, capacity_matrix) -Returns the maximum flow in the network, the flow matrix and -the partition {S,T} in the form of a vector of 1's and 2's. -The partition vector may also contain 0's. These can be -assigned any label (1 or 2), it is a user choice. +Compute the max-flow/min-cut between `source` and `target` for `residual_graph` +using the Boykov-Kolmogorov algorithm. -For further details, please refer to the paper: +Return the maximum flow in the network, the flow matrix and the partition +`{S,T}` in the form of a vector of 0's, 1's and 2's. -BOYKOV, Y.; KOLMOGOROV, V., 2004. An Experimental Comparison of +### References +- BOYKOV, Y.; KOLMOGOROV, V., 2004. An Experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision. -Uses a default capacity of 1 when the capacity matrix isn\'t specified. - -Requires arguments: -residual_graph::DiGraph # the input graph -source::Int # the source vertex -target::Int # the target vertex -capacity_matrix::AbstractArray{T,2} # edge flow capacities - -Author: Júlio Hoffimann Mendes (juliohm@stanford.edu) +### Author +- Júlio Hoffimann Mendes (juliohm@stanford.edu) """ - -function boykov_kolmogorov_impl{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2} # edge flow capacities +function boykov_kolmogorov_impl end +@traitfn function boykov_kolmogorov_impl( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix # edge flow capacities ) + T = eltype(capacity_matrix) + U = eltype(residual_graph) + n = nv(residual_graph) flow = 0 flow_matrix = zeros(T, n, n) - TREE = zeros(Int, n) - TREE[source] = 1 - TREE[target] = 2 + TREE = zeros(U, n) + TREE[source] = U(1) + TREE[target] = U(2) - PARENT = zeros(Int, n) + PARENT = zeros(U, n) A = [source,target] - O = Vector{Int}() + O = Vector{U}() while true - # growth stage - path = find_path!(residual_graph, source, target, flow_matrix, capacity_matrix, PARENT, TREE, A) + # growth stage + path = find_path!(residual_graph, source, target, flow_matrix, capacity_matrix, PARENT, TREE, A) - isempty(path) && break + isempty(path) && break - # augmentation stage - flow += augment!(path, flow_matrix, capacity_matrix, PARENT, TREE, O) + # augmentation stage + flow += augment!(path, flow_matrix, capacity_matrix, PARENT, TREE, O) - # adoption stage - adopt!(residual_graph, source, target, flow_matrix, capacity_matrix, PARENT, TREE, A, O) + # adoption stage + adopt!(residual_graph, source, target, flow_matrix, capacity_matrix, PARENT, TREE, A, O) end return flow, flow_matrix, TREE end -function find_path!{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - flow_matrix::AbstractArray{T,2}, # the current flow matrix - capacity_matrix::AbstractArray{T,2}, # edge flow capacities - PARENT::Vector{Int}, # parent table - TREE::Vector{Int}, # tree table - A::Vector{Int} # active set +@traitfn function find_path!( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + flow_matrix::AbstractMatrix, # the current flow matrix + capacity_matrix::AbstractMatrix, # edge flow capacities + PARENT::Vector, # parent table + TREE::Vector, # tree table + A::Vector # active set ) - - tree_cap(p,q) = TREE[p] == 1 ? capacity_matrix[p,q] - flow_matrix[p,q] : - capacity_matrix[q,p] - flow_matrix[q,p] + T = eltype(residual_graph) + tree_cap(p,q) = TREE[p] == one(T) ? capacity_matrix[p,q] - flow_matrix[p,q] : + capacity_matrix[q,p] - flow_matrix[q,p] while !isempty(A) - p = last(A) - for q in neighbors(residual_graph, p) - if tree_cap(p,q) > 0 - if TREE[q] == 0 - TREE[q] = TREE[p] - PARENT[q] = p - unshift!(A, q) - end - if TREE[q] ≠ 0 && TREE[q] ≠ TREE[p] - # p -> source - path_to_source = [p] - while PARENT[p] ≠ 0 - p = PARENT[p] - push!(path_to_source, p) - end - - # q -> target - path_to_target = [q] - while PARENT[q] ≠ 0 - q = PARENT[q] - push!(path_to_target, q) - end - - # source -> target - path = [reverse!(path_to_source); path_to_target] - - if path[1] == source && path[end] == target - return path - elseif path[1] == target && path[end] == source - return reverse!(path) + p = last(A) + for q in neighbors(residual_graph, p) + if tree_cap(p,q) > 0 + if TREE[q] == zero(T) + TREE[q] = TREE[p] + PARENT[q] = p + unshift!(A, q) + end + if TREE[q] ≠ zero(T) && TREE[q] ≠ TREE[p] + # p -> source + path_to_source = [p] + while PARENT[p] ≠ zero(T) + p = PARENT[p] + push!(path_to_source, p) + end + + # q -> target + path_to_target = [q] + while PARENT[q] ≠ zero(T) + q = PARENT[q] + push!(path_to_target, q) + end + + # source -> target + path = [reverse!(path_to_source); path_to_target] + + if path[1] == source && path[end] == target + return path + elseif path[1] == target && path[end] == source + return reverse!(path) + end + end end - end end - end - pop!(A) + pop!(A) end - return Vector{Int}() + return Vector{T}() end -function augment!{T<:Number}( +function augment!( path::AbstractVector, # path from source to target - flow_matrix::AbstractArray{T,2}, # the current flow matrix - capacity_matrix::AbstractArray{T,2}, # edge flow capacities - PARENT::Vector{Int}, # parent table - TREE::Vector{Int}, # tree table - O::Vector{Int} # orphan set + flow_matrix::AbstractMatrix, # the current flow matrix + capacity_matrix::AbstractMatrix, # edge flow capacities + PARENT::Vector, # parent table + TREE::Vector, # tree table + O::Vector # orphan set ) + T = eltype(path) # bottleneck capacity Δ = Inf for i=1:length(path)-1 - p, q = path[i:i+1] - cap = capacity_matrix[p,q] - flow_matrix[p,q] - cap < Δ && (Δ = cap) + p, q = path[i:i+1] + cap = capacity_matrix[p,q] - flow_matrix[p,q] + cap < Δ && (Δ = cap) end # update residual graph for i=1:length(path)-1 - p, q = path[i:i+1] - flow_matrix[p,q] += Δ - flow_matrix[q,p] -= Δ - - # create orphans - if flow_matrix[p,q] == capacity_matrix[p,q] - if TREE[p] == TREE[q] == 1 - PARENT[q] = 0 - unshift!(O, q) - end - if TREE[p] == TREE[q] == 2 - PARENT[p] = 0 - unshift!(O, p) + p, q = path[i:i+1] + flow_matrix[p,q] += Δ + flow_matrix[q,p] -= Δ + + # create orphans + if flow_matrix[p,q] == capacity_matrix[p,q] + if TREE[p] == TREE[q] == one(T) + PARENT[q] = zero(T) + unshift!(O, q) + end + if TREE[p] == TREE[q] == 2 + PARENT[p] = zero(T) + unshift!(O, p) + end end - end end return Δ end -function adopt!{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - flow_matrix::AbstractArray{T,2}, # the current flow matrix - capacity_matrix::AbstractArray{T,2}, # edge flow capacities - PARENT::Vector{Int}, # parent table - TREE::Vector{Int}, # tree table - A::Vector{Int}, # active set - O::Vector{Int} # orphan set +@traitfn function adopt!( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + flow_matrix::AbstractMatrix, # the current flow matrix + capacity_matrix::AbstractMatrix, # edge flow capacities + PARENT::Vector, # parent table + TREE::Vector, # tree table + A::Vector, # active set + O::Vector # orphan set ) + T = eltype(residual_graph) tree_cap(p,q) = TREE[p] == 1 ? capacity_matrix[p,q] - flow_matrix[p,q] : - capacity_matrix[q,p] - flow_matrix[q,p] + capacity_matrix[q,p] - flow_matrix[q,p] while !isempty(O) - p = pop!(O) - # try to find parent that is not an orphan - parent_found = false - for q in neighbors(residual_graph, p) - if TREE[q] == TREE[p] && tree_cap(q,p) > 0 - # check if "origin" is either source or target - o = q - while PARENT[o] ≠ 0 - o = PARENT[o] - end - if o == source || o == target - parent_found = true - PARENT[p] = q - break - end - end - end - - if !parent_found - # scan all neighbors and make the orphan a free node + p = pop!(O) + # try to find parent that is not an orphan + parent_found = false for q in neighbors(residual_graph, p) - if TREE[q] == TREE[p] - if tree_cap(q,p) > 0 - unshift!(A, q) - end - if PARENT[q] == p - PARENT[q] = 0 - unshift!(O, q) + if TREE[q] == TREE[p] && tree_cap(q,p) > 0 + # check if "origin" is either source or target + o = q + while PARENT[o] ≠ 0 + o = PARENT[o] + end + if o == source || o == target + parent_found = true + PARENT[p] = q + break + end end - end end - TREE[p] = 0 - B = setdiff(A, p) - resize!(A, length(B))[:] = B - end + if !parent_found + # scan all neighbors and make the orphan a free node + for q in neighbors(residual_graph, p) + if TREE[q] == TREE[p] + if tree_cap(q,p) > 0 + unshift!(A, q) + end + if PARENT[q] == p + PARENT[q] = zero(T) + unshift!(O, q) + end + end + end + + TREE[p] = zero(T) + B = setdiff(A, p) + resize!(A, length(B))[:] = B + end end end diff --git a/src/flow/dinic.jl b/src/flow/dinic.jl index 7101dc00c..c57d75e54 100644 --- a/src/flow/dinic.jl +++ b/src/flow/dinic.jl @@ -1,25 +1,20 @@ """ -Computes the maximum flow between the source and target vertexes in a flow -graph using [Dinic\'s Algorithm](https://en.wikipedia.org/wiki/Dinic%27s_algorithm) -Returns the value of the maximum flow as well as the final flow matrix. + function dinic_impl(residual_graph, source, target, capacity_matrix) -Use a default capacity of 1 when the capacity matrix isn\'t specified. - -Requires arguments: -residual_graph::DiGraph # the input graph -source::Int # the source vertex -target::Int # the target vertex -capacity_matrix::AbstractArray{T,2} # edge flow capacities +Compute the maximum flow between the `source` and `target` for `residual_graph` +with edge flow capacities in `capacity_matrix` using +[Dinic\'s Algorithm](https://en.wikipedia.org/wiki/Dinic%27s_algorithm). +Return the value of the maximum flow as well as the final flow matrix. """ - -function dinic_impl{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2} # edge flow capacities +function dinic_impl end +@traitfn function dinic_impl( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix # edge flow capacities ) n = nv(residual_graph) # number of vertexes - + T = eltype(capacity_matrix) flow_matrix = zeros(T, n, n) # initialize flow matrix P = zeros(Int, n) # Sharable parent vector @@ -33,58 +28,25 @@ function dinic_impl{T<:Number}( return flow, flow_matrix end -""" -Uses BFS to identify a blocking flow in -the input graph and then backtracks from the targetto the source, aumenting flow -along all possible paths. - -Requires arguments: -residual_graph::DiGraph # the input graph -source::Int # the source vertex -target::Int # the target vertex -capacity_matrix::AbstractArray{T,2} # edge flow capacities -flow_matrix::AbstractArray{T,2} # the current flow matrix -""" -function blocking_flow!{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2}, # edge flow capacities - flow_matrix::AbstractArray{T,2}, # the current flow matrix - ) - P = zeros(T, nv(residual_graph)) - return blocking_flow!(residual_graph, - source, - target, - capacity_matrix, - flow_matrix, - P) -end -"""blocking_flow! -Preallocated version of blocking_flow.Uses BFS to identify a blocking flow in -the input graph and then backtracks from the target to the source, aumenting flow -along all possible paths. - -Requires arguments: -residual_graph::DiGraph # the input graph -source::Int # the source vertex -target::Int # the target vertex -capacity_matrix::AbstractArray{T,2} # edge flow capacities -flow_matrix::AbstractArray{T,2} # the current flow matrix -P::AbstractArray{Int, 1} # Parent vector to store Level Graph + + """ + blocking_flow!(residual_graph, source, target, capacity_matrix, flow-matrix, P) -function blocking_flow!{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2}, # edge flow capacities - flow_matrix::AbstractArray{T,2}, # the current flow matrix - P::AbstractArray{Int, 1} # Parent vector to store Level Graph +Like `blocking_flow`, but requires a preallocated parent vector `P`. +""" +function blocking_flow! end +@traitfn function blocking_flow!( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_matrix::AbstractMatrix, # the current flow matrix + P::AbstractVector{Int} # Parent vector to store Level Graph ) n = nv(residual_graph) # number of vertexes - + T = eltype(capacity_matrix) fill!(P, -1) P[source] = -2 @@ -93,7 +55,7 @@ function blocking_flow!{T<:Number}( while length(Q) > 0 # Construct the Level Graph using BFS u = pop!(Q) - for v in fadj(residual_graph, u) + for v in out_neighbors(residual_graph, u) if P[v] == -1 && capacity_matrix[u,v] > flow_matrix[u,v] P[v] = u unshift!(Q, v) @@ -105,7 +67,7 @@ function blocking_flow!{T<:Number}( total_flow = 0 - for bv in badj(residual_graph, target) # Trace all possible routes to source + for bv in in_neighbors(residual_graph, target) # Trace all possible routes to source flow = typemax(T) v = target u = bv @@ -135,3 +97,24 @@ function blocking_flow!{T<:Number}( end return total_flow end + +""" + blocking_flow(residual_graph, source, target, capacity_matrix, flow-matrix) + +Use BFS to identify a blocking flow in the `residual_graph` with current flow +matrix `flow_matrix`and then backtrack from `target` to `source`, +augmenting flow along all possible paths. +""" +blocking_flow( + residual_graph::AbstractGraph, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_matrix::AbstractMatrix, # the current flow matrix + ) = blocking_flow!( + residual_graph, + source, + target, + capacity_matrix, + flow_matrix, + zeros(Int, nv(residual_graph))) diff --git a/src/flow/edmonds_karp.jl b/src/flow/edmonds_karp.jl index 3fb69ce2b..7160324b7 100644 --- a/src/flow/edmonds_karp.jl +++ b/src/flow/edmonds_karp.jl @@ -1,25 +1,22 @@ """ -Computes the maximum flow between the source and target vertexes in a flow -graph using the [Edmonds-Karp algorithm](https://en.wikipedia.org/wiki/Edmondss%E2%80%93Karp_algorithm). -Returns the value of the maximum flow as well as the final flow matrix. -Use a default capacity of 1 when the capacity matrix isn\'t specified. -Requires arguments: -- residual_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T,2} # edge flow capacities -""" + edmonds_karp_impl(residual_graph, source, target, capacity_matrix) -function edmonds_karp_impl{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2} # edge flow capacities +Compute the maximum flow in flow graph `residual_graph` between `source` and +`target` and capacities defined in `capacity_matrix` using the +[Edmonds-Karp algorithm](https://en.wikipedia.org/wiki/Edmondss%E2%80%93Karp_algorithm). +Return the value of the maximum flow as well as the final flow matrix. +""" +function edmonds_karp_impl end +@traitfn function edmonds_karp_impl( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix # edge flow capacities ) + T = eltype(capacity_matrix) n = nv(residual_graph) # number of vertexes flow = 0 flow_matrix = zeros(T, n, n) # initialize flow matrix - P = zeros(Int, n) S = zeros(Int, n) while true @@ -30,7 +27,7 @@ function edmonds_karp_impl{T<:Number}( if flag != 0 # no more valid paths break else - path = [v] # initialize path + path = [Int(v)] # initialize path sizehint!(path, n) u = v @@ -43,9 +40,9 @@ function edmonds_karp_impl{T<:Number}( u = v # trace path from v to target while u!=target u = S[u] - push!(path, u) + push!(path, Int(u)) end - # augment flow along path + # augment flow along path flow += augment_path!(path, flow_matrix, capacity_matrix) end end @@ -54,19 +51,17 @@ function edmonds_karp_impl{T<:Number}( end """ -Calculates the amount by which flow can be augmented in the given path. -Augments the flow and returns the augment value. -Requires arguments: -- path::Vector{Int} # input path -- flow_matrix::AbstractArray{T,2} # the current flow matrix -- capacity_matrix::AbstractArray{T,2} # edge flow capacities -""" + augment_path!(path, flow_matrix, capacity_matrix) -function augment_path!{T<:Number}( +Calculate the amount by which flow can be augmented in the given path. +Augment the flow and returns the augment value. +""" +function augment_path!( path::Vector{Int}, # input path - flow_matrix::AbstractArray{T,2}, # the current flow matrix - capacity_matrix::AbstractArray{T,2} # edge flow capacities + flow_matrix::AbstractMatrix, # the current flow matrix + capacity_matrix::AbstractMatrix # edge flow capacities ) + T = eltype(flow_matrix) augment = typemax(T) # initialize augment for i in 1:length(path)-1 # calculate min capacity along path u = path[i] @@ -85,57 +80,18 @@ function augment_path!{T<:Number}( end """ -Uses Bidirectional BFS to look for augmentable-paths. Returns the vertex where -the two BFS searches intersect, the Parent table of the path, the -Successor table of the path found, and a flag indicating success -Flag Values: -0 => success -1 => No Path to target -2 => No Path to source -""" -function fetch_path{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - flow_matrix::AbstractArray{T,2}, # the current flow matrix - capacity_matrix::AbstractArray{T,2} # edge flow capacities - ) - n = nv(residual_graph) - P = -1 * ones(Int, n) - S = -1 * ones(Int, n) - return fetch_path!(residual_graph, - source, - target, - flow_matrix, - capacity_matrix, - P, - S) -end + fetch_path!(residual_graph, source, target, flow_matrix, capacity_matrix, P, S) +Like `fetch_path`, but requires preallocated parent vector `P` and successor +vector `S`. """ -A preallocated version of fetch_paths. The parent and successor tables are pre-allocated. -Uses Bidirectional BFS to look for augmentable-paths. Returns the vertex where -the two BFS searches intersect, the Parent table of the path, the -Successor table of the path found, and a flag indicating success -Flag Values: -0 => success -1 => No Path to target -2 => No Path to source -Requires arguments: - residual_graph::DiGraph # the input graph - source::Int # the source vertex - target::Int # the target vertex - flow_matrix::AbstractArray{T,2} # the current flow matrix - capacity_matrix::AbstractArray{T,2} # edge flow capacities - P::Vector{Int} # parent table of path init to -1s - S::Vector{Int} # successor table of path init to -1s -""" -function fetch_path!{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - flow_matrix::AbstractArray{T,2}, # the current flow matrix - capacity_matrix::AbstractArray{T,2}, # edge flow capacities +function fetch_path! end +@traitfn function fetch_path!( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + flow_matrix::AbstractMatrix, # the current flow matrix + capacity_matrix::AbstractMatrix, # edge flow capacities P::Vector{Int}, # parent table of path init to -1s S::Vector{Int} # successor table of path init to -1s ) @@ -151,10 +107,9 @@ function fetch_path!{T<:Number}( sizehint!(Q_r, n) while true - if length(Q_f) <= length(Q_r) u = pop!(Q_f) - for v in fadj(residual_graph, u) + for v in out_neighbors(residual_graph, u) if capacity_matrix[u,v] - flow_matrix[u,v] > 0 && P[v] == -1 P[v] = u if S[v] == -1 @@ -168,7 +123,7 @@ function fetch_path!{T<:Number}( length(Q_f) == 0 && return 0, P, S, 1 # No paths to target else v = pop!(Q_r) - for u in badj(residual_graph, v) + for u in in_neighbors(residual_graph, v) if capacity_matrix[u,v] - flow_matrix[u,v] > 0 && S[u] == -1 S[u] = v P[u] != -1 && return u, P, S, 0 # 0 indicates success @@ -182,3 +137,34 @@ function fetch_path!{T<:Number}( end end end + + +""" + fetch_path(residual_graph, source, target, flow_matrix, capacity_matrix) + + +Use bidirectional BFS to look for augmentable paths from `source` to `target` in +`residual_graph`. Return the vertex where the two BFS searches intersect, +the parent table of the path, the successor table of the path found, and a +flag indicating success (0 => success; 1 => no path to target, 2 => no path +to source). +""" +function fetch_path end +@traitfn function fetch_path( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + flow_matrix::AbstractMatrix, # the current flow matrix + capacity_matrix::AbstractMatrix # edge flow capacities + ) + n = nv(residual_graph) + P = fill(-1, n) + S = fill(-1, n) + return fetch_path!(residual_graph, + source, + target, + flow_matrix, + capacity_matrix, + P, + S) +end diff --git a/src/flow/ext_multiroute_flow.jl b/src/flow/ext_multiroute_flow.jl index 3ae67341d..4dcd65ebb 100644 --- a/src/flow/ext_multiroute_flow.jl +++ b/src/flow/ext_multiroute_flow.jl @@ -1,256 +1,251 @@ """ -Computes the maximum multiroute flow (for any real number of routes) -between the source and target vertexes in a flow graph using the -[Extended Multiroute Flow algorithm](http://dx.doi.org/10.1016/j.disopt.2016.05.002). -If a number of routes is given, returns the value of the multiroute flow as -well as the final flow matrix, along with a multiroute cut if + emrf(flow_graph, source, target, capacity_matrix, flow_algorithm, routes=0) + +Compute the maximum multiroute flow (for any number of `route`s) +between `source` and `target` in `flow_graph` via flow algorithm `flow_algorithm`. + +If a number of routes is given, return the value of the multiroute flow as +well as the final flow matrix, along with a multiroute cut if the Boykov-Kolmogorov max-flow algorithm is used as a subroutine. -Otherwise, it returns the vector of breaking points of the parametric +Otherwise, return the vector of breaking points of the parametric multiroute flow function. -Use a default capacity of 1 when the capacity matrix isn\'t specified. -Requires arguments: -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T, 2} # edge flow capacities -- flow_algorithm::AbstractFlowAlgorithm # keyword argument for algorithm -- routes::Int # keyword argument for routes -""" +### References +- [Extended Multiroute Flow algorithm](http://dx.doi.org/10.1016/j.disopt.2016.05.002) +""" # EMRF (Extended Multiroute Flow) algorithms -function emrf{T<:AbstractFloat, R<:Real}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm - routes::R = 0 - ) - breakingpoints = breakingPoints(flow_graph, source, target, capacity_matrix) - if routes > zero(R) - x, f = intersection(breakingpoints, routes) - return maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = flow_algorithm, restriction = x) - end - return breakingpoints +function emrf( + flow_graph::AbstractGraph, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm + routes::Real = 0 + ) + breakingpoints = breakingPoints(flow_graph, source, target, capacity_matrix) + if routes > 0 + x, f = intersection(breakingpoints, routes) + return maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = flow_algorithm, restriction = x) + end + return breakingpoints end -""" -Output a set of (point,slope) that compose the restricted max-flow function. -One point by possible slope is enough (hence O(λ×max_flow) complexity). -Requires arguments: -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T, 2} # edge flow capacities -""" - -function auxiliaryPoints{T<:AbstractFloat}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2} # edge flow capacities - ) - # Problem descriptors - λ = maximum_flow(flow_graph, source, target)[1] # Connectivity - n = nv(flow_graph) # number of nodes - r1, r2 = minmaxCapacity(capacity_matrix) # restriction left (1) and right (2) - auxpoints = fill((0., 0.), λ + 1) +@doc_str """ + auxiliaryPoints(flow_graph, source, target, capacity_matrix) - # Initialisation of left side (1) - f1, F1, cut1 = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = BoykovKolmogorovAlgorithm(), restriction = r1) - s1 = slope(flow_graph, capacity_matrix, cut1, r1) # left slope - auxpoints[λ + 1 - s1] = (r1, f1) # Add left initial auxiliary point +Output a set of (point, slope) that compose the restricted max-flow function +of `flow_graph` from `source to `target` using capacities in `capacity_matrix`. - # Initialisation of right side (2) - f2, F2, cut2 = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = BoykovKolmogorovAlgorithm(), restriction = r2) - s2 = slope(flow_graph, capacity_matrix, cut2, r2) # right slope - auxpoints[λ + 1 - s2] = (r2, f2) # Add right initial auxiliary point - - # Loop if the slopes are distinct by at least 2 - if s1 > s2 + 1 - queue = [((f1, s1, r1), (f2, s2, r2))] - - while !isempty(queue) - # Computes an intersection (middle) with a new associated slope - (f1, s1, r1), (f2, s2, r2) = pop!(queue) - r, expectedflow = intersection(r1, f1, s1, r2, f2, s2) - f, F, cut = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = BoykovKolmogorovAlgorithm(), restriction = r) - s = slope(flow_graph, capacity_matrix, max(cut, 1), r) # current slope - auxpoints[λ + 1 - s] = (r, f) - # If the flow at the intersection (middle) is as expected - if expectedflow ≉ f # approximatively not equal (enforced by floating precision) - # if the slope difference between (middle) and left is at least 2 - # push (left),(middle) - if s1 > s + 1 && (r2, f2) ≉ (r, f) - q = (f1, s1, r1), (f, s, r) - push!(queue, q) - end - # if the slope difference between (middle) and right is at least 2 - # push (middle),(right) - if s > s2 + 1 && (r1, f1) ≉ (r, f) - q = (f, s, r), (f2, s2, r2) - push!(queue, q) +### Performance +One point by possible slope is enough (hence \mathcal{O}(λ×max_flow) complexity). +""" +function auxiliaryPoints end +@traitfn function auxiliaryPoints( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix # edge flow capacities + ) + # Problem descriptors + λ = maximum_flow(flow_graph, source, target)[1] # Connectivity + n = nv(flow_graph) # number of vertices + r1, r2 = minmaxCapacity(capacity_matrix) # restriction left (1) and right (2) + auxpoints = fill((0., 0.), λ + 1) + + # Initialisation of left side (1) + f1, F1, cut1 = maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = BoykovKolmogorovAlgorithm(), restriction = r1) + s1 = slope(flow_graph, capacity_matrix, cut1, r1) # left slope + auxpoints[λ + 1 - s1] = (r1, f1) # Add left initial auxiliary point + + # Initialisation of right side (2) + f2, F2, cut2 = maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = BoykovKolmogorovAlgorithm(), restriction = r2) + s2 = slope(flow_graph, capacity_matrix, cut2, r2) # right slope + auxpoints[λ + 1 - s2] = (r2, f2) # Add right initial auxiliary point + + # Loop if the slopes are distinct by at least 2 + if s1 > s2 + 1 + queue = [((f1, s1, r1), (f2, s2, r2))] + + while !isempty(queue) + # Computes an intersection (middle) with a new associated slope + (f1, s1, r1), (f2, s2, r2) = pop!(queue) + r, expectedflow = intersection(r1, f1, s1, r2, f2, s2) + f, F, cut = maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = BoykovKolmogorovAlgorithm(), restriction = r) + s = slope(flow_graph, capacity_matrix, max.(cut, 1), r) # current slope + auxpoints[λ + 1 - s] = (r, f) + # If the flow at the intersection (middle) is as expected + if expectedflow ≉ f # approximatively not equal (enforced by floating precision) + # if the slope difference between (middle) and left is at least 2 + # push (left),(middle) + if s1 > s + 1 && (r2, f2) ≉ (r, f) + q = (f1, s1, r1), (f, s, r) + push!(queue, q) + end + # if the slope difference between (middle) and right is at least 2 + # push (middle),(right) + if s > s2 + 1 && (r1, f1) ≉ (r, f) + q = (f, s, r), (f2, s2, r2) + push!(queue, q) + end + end end - end end - end - return auxpoints + return auxpoints end """ -Calculates the breaking of the restricted max-flow from a set of auxiliary points. -Requires arguments: -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T, 2} # edge flow capacities -""" - -function breakingPoints{T<:AbstractFloat}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2} # edge flow capacities - ) - auxpoints = auxiliaryPoints(flow_graph, source, target, capacity_matrix) - λ = length(auxpoints) - 1 - left_index = 1 - breakingpoints = Vector{Tuple{T, T, Int}}() + breakingPoints(flow_graph::::IsDirected, source, target, capacity_matrix) - for (id, point) in enumerate(auxpoints) - if id == 1 - push!(breakingpoints, (0., 0., λ)) - else - pleft = breakingpoints[left_index] - if point[1] != 0 - x, y = intersection(pleft[1], pleft[2], pleft[3], - point[1], point[2], λ + 1 - id) - push!(breakingpoints,(x, y, λ + 1 - id)) - left_index += 1 - end +Calculates the breaking of the restricted max-flow from a set of auxiliary points +for `flow_graph` from `source to `target` using capacities in `capacity_matrix`. +""" +function breakingPoints end +@traitfn function breakingPoints( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix # edge flow capacities + ) + auxpoints = auxiliaryPoints(flow_graph, source, target, capacity_matrix) + λ = length(auxpoints) - 1 + left_index = 1 + T = eltype(capacity_matrix) + breakingpoints = Vector{Tuple{T, T, Int}}() + + for (id, point) in enumerate(auxpoints) + if id == 1 + push!(breakingpoints, (0., 0., λ)) + else + pleft = breakingpoints[left_index] + if point[1] != 0 + x, y = intersection(pleft[1], pleft[2], pleft[3], + point[1], point[2], λ + 1 - id) + push!(breakingpoints,(x, y, λ + 1 - id)) + left_index += 1 + end + end end - end - return breakingpoints + return breakingpoints end """ -Function to get the nonzero min and max function of a Matrix. + minmaxCapacity(capacity_matrix) -Note: this is more efficient than maximum() / minimum() / extrema() -since we have to ignore zero values.since we have to ignore zero values. +Return the nonzero min and max function of `capacity_matrix`. -Requires argument: -- capacity_matrix::AbstractArray{T, 2} # edge flow capacities +Note: this is more efficient than maximum() / minimum() / extrema() +since we have to ignore zero values. """ # Function to get the nonzero min and max function of a Matrix # note: this is more efficient than maximum() / minimum() / extrema() # since we have to ignore zero values. -function minmaxCapacity{T<:AbstractFloat}( - capacity_matrix::AbstractArray{T, 2} # edge flow capacities - ) - cmin, cmax = typemax(T), typemin(T) - for c in capacity_matrix - if c > zero(T) - cmin = min(cmin, c) +function minmaxCapacity( + capacity_matrix::AbstractMatrix # edge flow capacities + ) + T = eltype(capacity_matrix) + cmin, cmax = typemax(T), typemin(T) + for c in capacity_matrix + if c > zero(T) + cmin = min(cmin, c) + end + cmax = max(cmax, c) end - cmax = max(cmax, c) - end - return cmin, cmax + return cmin, cmax end """ -Function to get the slope of the restricted flow. The slope is initialized at 0 -and is incremented for each non saturated edge in the restricted min-cut. -Requires argument: - flow_graph::DiGraph, # the input graph - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - cut::Vector{Int}, # cut information for vertices - restriction::T # value of the restriction + slope(flow_graph, capacity_matrix, cut, restriction) + +Return the slope of `flow_graph` using capacities in `capacity_matrix` and +a cut vector `cut`. The slope is initialized at 0 and is incremented for +each edge whose capacity does not exceed `restriction`. """ +function slope end # Function to get the slope of the restricted flow -function slope{T<:AbstractFloat}( - flow_graph::DiGraph, # the input graph - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - cut::Vector{Int}, # cut information for vertices - restriction::T # value of the restriction - ) - slope = 0 - for e in edges(flow_graph) - ## Chain comparison to wether an edge cross the cut from the source side of - # the cut to the target side of the cut. Then the edge is selected iff the - # capacity of the edge is larger then the restriction argument. - # cut[dst(e)] == 2 > cut[src(e)] is equivalent to - # cut[dst(e)] == 2 && 2 > cut[src(e)] - # Description of chain comparisons can be found at https://goo.gl/IJpCqe - if cut[dst(e)] == 2 > cut[src(e)] && - capacity_matrix[src(e), dst(e)] > restriction - slope += 1 +@traitfn function slope( + flow_graph::::IsDirected, # the input graph + capacity_matrix::AbstractMatrix, # edge flow capacities + cut::Vector, # cut information for vertices + restriction::Number # value of the restriction + ) + slope = 0 + for e in edges(flow_graph) + ## Chain comparison to wether an edge cross the cut from the source side of + # the cut to the target side of the cut. Then the edge is selected iff the + # capacity of the edge is larger then the restriction argument. + # cut[dst(e)] == 2 > cut[src(e)] is equivalent to + # cut[dst(e)] == 2 && 2 > cut[src(e)] + # Description of chain comparisons can be found at https://goo.gl/IJpCqe + if cut[dst(e)] == 2 > cut[src(e)] && + capacity_matrix[src(e), dst(e)] > restriction + slope += 1 + end end - end - return slope + return slope end """ -Computes the intersection between: -1) Two lines + intersection(x1, y1, a1, x2, y2, a2) + +Return the intersection of two lines defined by `x` and `y` with slopes `a`. 2) A set of segments and a linear function of slope k passing by the origin. Requires argument: 1) - x1, y1, a1, x2, y2, a2::T<:AbstractFloat # Coordinates/slopes 2) - points::Vector{Tuple{T, T, Int}} # vector of points with T<:AbstractFloat - - k::R<:Real # number of routes (slope of the line) +- k::R<:Real # number of routes (slope of the line) """ +function intersection( + x1::T, # x coordinate of point 1 + y1::T, # y coordinate of point 1 + a1::Integer, # slope passing by point 1 + x2::T, # x coordinate of point 2 + y2::T, # y coordinate of point 2 + a2::R # slope passing by point 2 + ) where T<:AbstractFloat where R<:Real + + (a1 == a2) && return -1., -1. # result will be ignored in other intersection method + b1 = y1 - a1 * x1 + b2 = y2 - a2 * x2 + x = (b2 - b1) / (a1 - a2) + y = a1 * x + b1 + return x, y +end -# Compute the (expected) intersection of two lines -function intersection{T<:AbstractFloat, R<:Real}( - x1::T, # x coordinate of point 1 - y1::T, # y coordinate of point 1 - a1::Int, # slope passing by point 1 - x2::T, # x coordinate of point 2 - y2::T, # y coordinate of point 2 - a2::R # slope passing by point 2 - ) - (a1 == a2) && return -1., -1. # result will be ignored in other intersection method - b1 = y1 - a1 * x1 - b2 = y2 - a2 * x2 - x = (b2 - b1) / (a1 - a2) - y = a1 * x + b1 - return x, y -end -# Compute the intersection between a set of segment and a line of slope k passing by the origin -function intersection{T<:AbstractFloat, R<:Real}( - points::Vector{Tuple{T, T, Int}}, # vector of breaking points - k::R # number of routes (slope of the line) - ) - λ = points[1][1] # Connectivity +""" + intersection(points, k) - # Loop over the segments (pair of breaking points) - for (id, p) in enumerate(points[1:(end - 1)]) - if id == 1 - (k ≈ λ) && return points[2] - else - x, y = intersection(p[1], p[2], p[3], 0., 0., k) - (p[1] ≤ x ≤ points[id + 1][1]) && return x, y +Return the intersection of a set of line segments and a line of slope `k` +passing by the origin. Segments are defined as a triple (x, y, slope). +""" +function intersection( + points::Vector{Tuple{T, T, I}}, # vector of breaking points + k::R # number of routes (slope of the line) + ) where T<:AbstractFloat where I<:Integer where R<:Real + λ = points[1][1] # Connectivity + + # Loop over the segments (pair of breaking points) + for (id, p) in enumerate(points[1:(end - 1)]) + if id == 1 + (k ≈ λ) && return points[2] + else + x, y = intersection(p[1], p[2], p[3], 0., 0., k) + (p[1] ≤ x ≤ points[id + 1][1]) && return x, y + end end - end - p = points[end] - return intersection(p[1], p[2], p[3], 0., 0., k) + p = points[end] + return intersection(p[1], p[2], p[3], 0., 0., k) end """ -Redefinition of ≈ (isapprox) for a pair of points -Requires argument: - a::Tuple{T, T}, # Point A with floating coordinates - b::Tuple{T, T} # Point B with floating coordinates + ≈(a, b) + +Redefinition of ≈ (isapprox) for a pair of points `a` and `b`. """ -function ≈{T<:AbstractFloat}( - a::Tuple{T, T}, # Point A with floating coordinates - b::Tuple{T, T} # Point B with floating coordinates - ) - return a[1] ≈ b[1] && a[2] ≈ b[2] -end +≈(a::Tuple{T, T}, b::Tuple{T, T}) where T <: AbstractFloat = + a[1] ≈ b[1] && a[2] ≈ b[2] diff --git a/src/flow/kishimoto.jl b/src/flow/kishimoto.jl index d2a371e39..2edcd2ef4 100644 --- a/src/flow/kishimoto.jl +++ b/src/flow/kishimoto.jl @@ -1,75 +1,70 @@ # Method when using Boykov-Kolmogorov as a subroutine # Kishimoto algorithm -function kishimoto{T<:AbstractFloat}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::BoykovKolmogorovAlgorithm, # keyword argument for algorithm - routes::Int # keyword argument for routes - ) - # Initialisation - flow, F, labels = maximum_flow(flow_graph, source, target, - capacity_matrix, algorithm = flow_algorithm) - restriction = flow / routes - flow, F, labels = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = flow_algorithm, restriction = restriction) - # Loop condition : approximatively not equal is enforced by floating precision - i = 1 - while flow < routes * restriction && flow ≉ routes * restriction - restriction = (flow - i * restriction) / (routes - i) - i += 1 +@traitfn function kishimoto( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_algorithm::BoykovKolmogorovAlgorithm, # keyword argument for algorithm + routes::Int # keyword argument for routes + ) + # Initialisation + flow, F, labels = maximum_flow(flow_graph, source, target, + capacity_matrix, algorithm = flow_algorithm) + restriction = flow / routes flow, F, labels = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = flow_algorithm, restriction = restriction) - end + algorithm = flow_algorithm, restriction = restriction) - # End - return flow, F, labels + # Loop condition : approximatively not equal is enforced by floating precision + i = 1 + while flow < routes * restriction && flow ≉ routes * restriction + restriction = (flow - i * restriction) / (routes - i) + i += 1 + flow, F, labels = maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = flow_algorithm, restriction = restriction) + end + + # End + return flow, F, labels end """ -Computes the maximum multiroute flow (for an integer number of routes) -between the source and target vertexes in a flow graph using the -[Kishimoto algorithm](http://dx.doi.org/10.1109/ICCS.1992.255031). -Returns the value of the multiroute flow as well as the final flow matrix, + kishimoto(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) + +Compute the maximum multiroute flow (for an integer number of `route`s) +between `source` and `target` in `flow_graph` with capacities in `capacity_matrix` +using the [Kishimoto algorithm](http://dx.doi.org/10.1109/ICCS.1992.255031). +Return the value of the multiroute flow as well as the final flow matrix, along with a multiroute cut if Boykov-Kolmogorov is used as a subroutine. -Use a default capacity of 1 when the capacity matrix isn\'t specified. -Requires arguments: -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T, 2} # edge flow capacities -- flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm -- routes::Int # keyword argument for routes """ +function kishimoto end +@traitfn function kishimoto( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm + routes::Int # keyword argument for routes + ) + # Initialisation + flow, F = maximum_flow(flow_graph, source, target, + capacity_matrix, algorithm = flow_algorithm) + restriction = flow / routes -function kishimoto{T<:AbstractFloat}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm - routes::Int # keyword argument for routes - ) - # Initialisation - flow, F = maximum_flow(flow_graph, source, target, - capacity_matrix, algorithm = flow_algorithm) - restriction = flow / routes - - flow, F = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = flow_algorithm, restriction = restriction) - - # Loop condition : approximatively not equal is enforced by floating precision - i = 1 - while flow < routes * restriction && flow ≉ routes * restriction - restriction = (flow - i * restriction) / (routes - i) - i += 1 flow, F = maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = flow_algorithm, restriction = restriction) - end + algorithm = flow_algorithm, restriction = restriction) + + # Loop condition : approximatively not equal is enforced by floating precision + i = 1 + while flow < routes * restriction && flow ≉ routes * restriction + restriction = (flow - i * restriction) / (routes - i) + i += 1 + flow, F = maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = flow_algorithm, restriction = restriction) + end - # End - return flow, F + # End + return flow, F end diff --git a/src/flow/maximum_flow.jl b/src/flow/maximum_flow.jl index dbda84c80..9dc5ef931 100644 --- a/src/flow/maximum_flow.jl +++ b/src/flow/maximum_flow.jl @@ -1,74 +1,79 @@ """ -Abstract type that allows users to pass in their preferred Algorithm + AbstractFlowAlgorithm + +Abstract type that allows users to pass in their preferred algorithm """ -abstract AbstractFlowAlgorithm +abstract type AbstractFlowAlgorithm end """ + EdmondsKarpAlgorithm <: AbstractFlowAlgorithm + Forces the maximum_flow function to use the Edmonds–Karp algorithm. """ -type EdmondsKarpAlgorithm <: AbstractFlowAlgorithm -end +struct EdmondsKarpAlgorithm <: AbstractFlowAlgorithm end """ -Forces the maximum_flow function to use Dinic\'s algorithm. + DinicAlgorithm <: AbstractFlowAlgorithm + +Forces the maximum_flow function to use Dinic's algorithm. """ -type DinicAlgorithm <: AbstractFlowAlgorithm -end +struct DinicAlgorithm <: AbstractFlowAlgorithm end """ + BoykovKolmogorovAlgorithm <: AbstractFlowAlgorithm + Forces the maximum_flow function to use the Boykov-Kolmogorov algorithm. """ -type BoykovKolmogorovAlgorithm <: AbstractFlowAlgorithm -end +struct BoykovKolmogorovAlgorithm <: AbstractFlowAlgorithm end """ Forces the maximum_flow function to use the Push-Relabel algorithm. """ -type PushRelabelAlgorithm <: AbstractFlowAlgorithm -end +struct PushRelabelAlgorithm <: AbstractFlowAlgorithm end """ -Type that returns 1 if a forward edge exists, and 0 otherwise -""" + DefaultCapacity{T} -type DefaultCapacity <: AbstractArray{Int, 2} +Structure that returns `1` if a forward edge exists in `flow_graph`, and `0` otherwise. +""" +struct DefaultCapacity{T<:Integer} <: AbstractMatrix{T} flow_graph::DiGraph - nv::Int - DefaultCapacity(flow_graph::DiGraph) = new(flow_graph, nv(flow_graph)) + nv::T end -getindex(d::DefaultCapacity, s::Int, t::Int) = if has_edge(d.flow_graph, s , t) 1 else 0 end -size(d::DefaultCapacity) = (d.nv, d.nv) +@traitfn DefaultCapacity(flow_graph::::IsDirected) = + DefaultCapacity(DiGraph(flow_graph), nv(flow_graph)) + +getindex(d::DefaultCapacity{T}, s::Integer, t::Integer) where T = if has_edge(d.flow_graph, s , t) one(T) else zero(T) end +# isassigned{T<:Integer}(d::DefaultCapacity{T}, u::T, v::T) = (u in 1:d.nv) && (v in 1:d.nv) +size(d::DefaultCapacity) = (Int(d.nv), Int(d.nv)) transpose(d::DefaultCapacity) = DefaultCapacity(reverse(d.flow_graph)) ctranspose(d::DefaultCapacity) = DefaultCapacity(reverse(d.flow_graph)) """ -Constructs a residual graph for the input flow graph. Creates a new graph instead -of modifying the input flow graph. + residual(flow_graph) -The residual graph comprises of the same Vertex list, but ensures that for each -edge (u,v), (v,u) also exists in the graph. (to allow flow in the reverse direction). +Return a directed residual graph for a directed `flow_graph`. -If only the forward edge exists, a reverse edge is created with capacity 0. If both -forward and reverse edges exist, their capacities are left unchanged. Since the capacities -in DefaultDistance cannot be changed, an array of ones is created. Returns the -residual graph and the modified capacity_matrix (when DefaultDistance is used.) +The residual graph comprises the same node list as the orginal flow graph, but +ensures that for each edge (u,v), (v,u) also exists in the graph. This allows +flow in the reverse direction. -Requires arguments: - -- flow_graph::DiGraph, # the input graph -- capacity_matrix::AbstractArray{T,2} # input capacity matrix +If only the forward edge exists, a reverse edge is created with capacity 0. +If both forward and reverse edges exist, their capacities are left unchanged. +Since the capacities in [`DefaultDistance`](@ref) cannot be changed, an array of ones +is created. """ - -residual(flow_graph::DiGraph) = DiGraph(Graph(flow_graph)) +function residual end +@traitfn residual(flow_graph::::IsDirected) = DiGraph(Graph(flow_graph)) # Method for Edmonds–Karp algorithm -function maximum_flow{T<:Number}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2}, # edge flow capacities +@traitfn function maximum_flow( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities algorithm::EdmondsKarpAlgorithm # keyword argument for algorithm ) residual_graph = residual(flow_graph) @@ -77,11 +82,11 @@ end # Method for Dinic's algorithm -function maximum_flow{T<:Number}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2}, # edge flow capacities +@traitfn function maximum_flow( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities algorithm::DinicAlgorithm # keyword argument for algorithm ) residual_graph = residual(flow_graph) @@ -90,11 +95,11 @@ end # Method for Boykov-Kolmogorov algorithm -function maximum_flow{T<:Number}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2}, # edge flow capacities +@traitfn function maximum_flow( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities algorithm::BoykovKolmogorovAlgorithm # keyword argument for algorithm ) residual_graph = residual(flow_graph) @@ -103,11 +108,11 @@ end # Method for Push-relabel algorithm -function maximum_flow{T<:Number}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2}, # edge flow capacities +@traitfn function maximum_flow( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities algorithm::PushRelabelAlgorithm # keyword argument for algorithm ) residual_graph = residual(flow_graph) @@ -115,71 +120,61 @@ function maximum_flow{T<:Number}( end """ -Generic maximum_flow function. Requires arguments: + maximum_flow(flow_graph, source, target[, capacity_matrix][, algorithm][, restriction]) -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T,2} # edge flow capacities -- algorithm::AbstractFlowAlgorithm # keyword argument for algorithm -- restriction::T # keyword argument for a restriction +Generic maximum_flow function for `flow_graph` from `source` to `target` with +capacities in `capacity_matrix`. +Uses flow algorithm `algorithm` and cutoff restriction `restriction`. -The function defaults to the Push-relabel algorithm. Alternatively, the algorithm -to be used can also be specified through a keyword argument. A default capacity of 1 -is assumed for each link if no capacity matrix is provided. -If the restriction is bigger than 0, it is applied to capacity_matrix. +- If `capacity_matrix` is not specified, `DefaultCapacity(flow_graph)` will be used. +- If `algorithm` is not specified, it will default to [`PushRelabelAlgorithm`](@ref). +- If `restriction` is not specified, it will default to `0`. -All algorithms return a tuple with 1) the maximum flow and 2) the flow matrix. -For the Boykov-Kolmogorov algorithm, the associated mincut is returned as a third output. +Return a tuple of (maximum flow, flow matrix). For the Boykov-Kolmogorov +algorithm, the associated mincut is returned as a third output. ### Usage Example: -```julia - -# Create a flow-graph and a capacity matrix -flow_graph = DiGraph(8) -flow_edges = [ - (1,2,10),(1,3,5),(1,4,15),(2,3,4),(2,5,9), - (2,6,15),(3,4,4),(3,6,8),(4,7,16),(5,6,15), - (5,8,10),(6,7,15),(6,8,10),(7,3,6),(7,8,10) +```jldoctest +julia> flow_graph = DiGraph(8) # Create a flow-graph +julia> flow_edges = [ +(1,2,10),(1,3,5),(1,4,15),(2,3,4),(2,5,9), +(2,6,15),(3,4,4),(3,6,8),(4,7,16),(5,6,15), +(5,8,10),(6,7,15),(6,8,10),(7,3,6),(7,8,10) ] -capacity_matrix = zeros(Int, 8, 8) -for e in flow_edges + +julia> capacity_matrix = zeros(Int, 8, 8) # Create a capacity matrix + +julia> for e in flow_edges u, v, f = e add_edge!(flow_graph, u, v) capacity_matrix[u,v] = f end -# Run default maximum_flow without the capacity_matrix -f, F = maximum_flow(flow_graph, 1, 8) +julia> f, F = maximum_flow(flow_graph, 1, 8) # Run default maximum_flow without the capacity_matrix -# Run default maximum_flow with the capacity_matrix -f, F = maximum_flow(flow_graph, 1, 8) +julia> f, F = maximum_flow(flow_graph, 1, 8) # Run default maximum_flow with the capacity_matrix -# Run Endmonds-Karp algorithm -f, F = maximum_flow(flow_graph,1,8,capacity_matrix,algorithm=EdmondsKarpAlgorithm()) +julia> f, F = maximum_flow(flow_graph,1,8,capacity_matrix,algorithm=EdmondsKarpAlgorithm()) # Run Edmonds-Karp algorithm -# Run Dinic's algorithm -f, F = maximum_flow(flow_graph,1,8,capacity_matrix,algorithm=DinicAlgorithm()) +julia> f, F = maximum_flow(flow_graph,1,8,capacity_matrix,algorithm=DinicAlgorithm()) # Run Dinic's algorithm -# Run Boykov-Kolmogorov algorithm -f, F, labels = maximum_flow(flow_graph,1,8,capacity_matrix,algorithm=BoykovKolmogorovAlgorithm()) +julia> f, F, labels = maximum_flow(flow_graph,1,8,capacity_matrix,algorithm=BoykovKolmogorovAlgorithm()) # Run Boykov-Kolmogorov algorithm ``` """ - -function maximum_flow{T<:Number}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2} = # edge flow capacities - DefaultCapacity(flow_graph); +function maximum_flow( + flow_graph::AbstractGraph, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix = # edge flow capacities + DefaultCapacity(flow_graph); algorithm::AbstractFlowAlgorithm = # keyword argument for algorithm - PushRelabelAlgorithm(), - restriction::T = zero(T) # keyword argument for restriction max-flow + PushRelabelAlgorithm(), + restriction::Real = 0 # keyword argument for restriction max-flow ) - if restriction > zero(T) - return maximum_flow(flow_graph, source, target, min(restriction, capacity_matrix), algorithm) + if restriction > 0 + return maximum_flow(flow_graph, source, target, min.(restriction, capacity_matrix), algorithm) end return maximum_flow(flow_graph, source, target, capacity_matrix, algorithm) end diff --git a/src/flow/multiroute_flow.jl b/src/flow/multiroute_flow.jl index 7ff052b70..90449150b 100644 --- a/src/flow/multiroute_flow.jl +++ b/src/flow/multiroute_flow.jl @@ -1,219 +1,228 @@ """ -Abstract type that allows users to pass in their preferred Algorithm + AbstractMultirouteFlowAlgorithm + +Abstract type that allows users to pass in their preferred algorithm. """ -abstract AbstractMultirouteFlowAlgorithm +abstract type AbstractMultirouteFlowAlgorithm end """ -Forces the multiroute_flow function to use the Kishimoto algorithm. + KishimotoAlgorithm + +Used to specify the Kishimoto algorithm. """ -type KishimotoAlgorithm <: AbstractMultirouteFlowAlgorithm -end +struct KishimotoAlgorithm <: AbstractMultirouteFlowAlgorithm end """ -Forces the multiroute_flow function to use the Extended Multiroute Flow algorithm. + ExtendedMultirouteFlowAlgorithm + +Used to specify the Extended Multiroute Flow algorithm. """ -type ExtendedMultirouteFlowAlgorithm <: AbstractMultirouteFlowAlgorithm -end +struct ExtendedMultirouteFlowAlgorithm <: AbstractMultirouteFlowAlgorithm end # Methods when the number of routes is more than the connectivity # 1) When using Boykov-Kolmogorov as a flow subroutine # 2) Other flow algorithm -function empty_flow{T<:Real}( - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::BoykovKolmogorovAlgorithm # keyword argument for algorithm - ) - n = size(capacity_matrix, 1) - return zero(T), zeros(T, n, n), zeros(T, n) +function empty_flow( + capacity_matrix::AbstractMatrix{T}, # edge flow capacities + flow_algorithm::BoykovKolmogorovAlgorithm # keyword argument for algorithm + ) where T<:Real + n = size(capacity_matrix, 1) + return zero(T), zeros(T, n, n), zeros(T, n) end # 2) Other flow algorithm -function empty_flow{T<:Real}( - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::AbstractFlowAlgorithm # keyword argument for algorithm - ) - n = size(capacity_matrix, 1) - return zero(T), zeros(T, n, n) +function empty_flow( + capacity_matrix::AbstractMatrix{T}, # edge flow capacities + flow_algorithm::AbstractFlowAlgorithm # keyword argument for algorithm + ) where T<:Real + n = size(capacity_matrix, 1) + return zero(T), zeros(T, n, n) end # Method for Kishimoto algorithm -function multiroute_flow{T<:Real}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm - mrf_algorithm::KishimotoAlgorithm, # keyword argument for algorithm - routes::Int # keyword argument for routes - ) - return kishimoto(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) +@traitfn function multiroute_flow( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm + mrf_algorithm::KishimotoAlgorithm, # keyword argument for algorithm + routes::Int # keyword argument for routes + ) + return kishimoto(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) end ## Methods for Extended Multiroute Flow Algorithm #1 When the breaking points are not already known -function multiroute_flow{T<:Real, R<:Real}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2}, # edge flow capacities - flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm - mrf_algorithm::ExtendedMultirouteFlowAlgorithm, # keyword argument for algorithm - routes::R # keyword argument for routes - ) - return emrf(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) +@traitfn function multiroute_flow( + flow_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix, # edge flow capacities + flow_algorithm::AbstractFlowAlgorithm, # keyword argument for algorithm + mrf_algorithm::ExtendedMultirouteFlowAlgorithm, # keyword argument for algorithm + routes::Real # keyword argument for routes + ) + return emrf(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) end #2 When the breaking points are already known #2-a Output: flow value (paired with the associated restriction) -function multiroute_flow{T<:Real, R<:Real}( - breakingpoints::Vector{Tuple{T, T, Int}}, # vector of breaking points - routes::R # keyword argument for routes - ) - return intersection(breakingpoints, routes) -end +multiroute_flow( + breakingpoints::Vector{Tuple{T, T, Int}}, # vector of breaking points + routes::R # keyword argument for routes + ) where T<:Real where R<:Real = + intersection(breakingpoints, routes) + #2-b Output: flow value, flows(, labels) -function multiroute_flow{T1<:Real, T2<:Real, R<:Real}( - breakingpoints::Vector{Tuple{T1, T1, Int}}, # vector of breaking points - routes::R, # keyword argument for routes - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T2, 2} = # edge flow capacities +function multiroute_flow( + breakingpoints::AbstractVector{Tuple{T1, T1, Int}}, # vector of breaking points + routes::R, # keyword argument for routes + flow_graph::AbstractGraph, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix = # edge flow capacities DefaultCapacity(flow_graph); - flow_algorithm::AbstractFlowAlgorithm = # keyword argument for algorithm + flow_algorithm::AbstractFlowAlgorithm = # keyword argument for algorithm PushRelabelAlgorithm() - ) - x, f = intersection(breakingpoints, routes) - - # For other cases, capacities need to be Floats - if !(T2<:AbstractFloat) - capacity_matrix = convert(AbstractArray{Float64, 2}, capacity_matrix) - end - - return maximum_flow(flow_graph, source, target, capacity_matrix, - algorithm = flow_algorithm, restriction = x) + ) where T1<:Real where R<:Real + x, f = intersection(breakingpoints, routes) + T2 = eltype(capacity_matrix) + # For other cases, capacities need to be Floats + if !(T2<:AbstractFloat) + capacity_matrix = convert(AbstractMatrix{Float64}, capacity_matrix) + end + + return maximum_flow(flow_graph, source, target, capacity_matrix, + algorithm = flow_algorithm, restriction = x) end +### TODO: CLEAN UP THIS FUNCTION AND DOCUMENTATION. THERE SHOULD BE NO NEED TO +### HAVE A TYPE-UNSTABLE FUNCTION HERE. (sbromberger 2017-03-26) """ -The generic multiroute_flow function will output three kinds of results: + multiroute_flow(flow_graph, source, target[, DefaultCapacity][, flow_algorithm][, mrf_algorithm][, routes]) -- When the number of routes is 0 or non-specified, the set of breaking points of -the multiroute flow is returned. -- When the input is limited to a set of breaking points and a route value k, -only the value of the k-route flow is returned +The generic multiroute_flow function. + +The output will vary depending on the input: + +- When the number of `route`s is `0`, return the set of breaking points of +the multiroute flow. +- When the number of `route`s is `1`, return a flow with a set of 1-disjoint paths +(this is the classical max-flow implementation). +- When the input is limited to a set of breaking points and a route value `k`, +return only the k-route flow. - Otherwise, a tuple with 1) the maximum flow and 2) the flow matrix. When the max-flow subroutine is the Boykov-Kolmogorov algorithm, the associated mincut is returned as a third output. When the input is a network, it requires the following arguments: -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T, 2} # edge flow capacities with T<:Real -- flow_algorithm::AbstractFlowAlgorithm # keyword argument for flow algorithm -- mrf_algorithm::AbstractFlowAlgorithm # keyword argument for multiroute flow algorithm -- routes::R<:Real # keyword argument for the number of routes +- `flow_graph`: the input graph +- `source`: the source vertex +- `target`: the target vertex +- `capacity_matrix`: matrix of edge flow capacities +- `flow_algorithm`: keyword argument for flow algorithm +- `mrf_algorithm`: keyword argument for multiroute flow algorithm +- `routes`: keyword argument for the number of routes When the input is only the set of (breaking) points and the number of route, it requires the following arguments: -- breakingpoints::Vector{Tuple{T, T, Int}}, # vector of breaking points -- routes::R<:Real, # number of routes +- `breakingpoints`: vector of breaking points +- `routes`: number of routes When the input is the set of (breaking) points, the number of routes, and the network descriptors, it requires the following arguments: -- breakingpoints::Vector{Tuple{T1, T1, Int}} # vector of breaking points (T1<:Real) -- routes::R<:Real # number of routes -- flow_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T2, 2} # optional edge flow capacities (T2<:Real) -- flow_algorithm::AbstractFlowAlgorithm # keyword argument for algorithm +- `breakingpoints`: vector of breaking points +- `routes`: number of routes +- `flow_graph`: the input graph +- `source`: the source vertex +- `target`: the target vertex +- `capacity_matrix`: matrix of edge flow capacities +- `flow_algorithm`: keyword argument for flow algorithm The function defaults to the Push-relabel (classical flow) and Kishimoto (multiroute) algorithms. Alternatively, the algorithms to be used can also -be specified through keyword arguments. A default capacity of 1 is assumed +be specified through keyword arguments. A default capacity of `1` is assumed for each link if no capacity matrix is provided. -The mrf_algorithm keyword is inforced to Extended Multiroute Flow +The `mrf_algorithm` keyword is inforced to Extended Multiroute Flow in the following cases: - The number of routes is non-integer - The number of routes is 0 or non-specified ### Usage Example : -(please consult the max_flow section for options about flow_algorithm +(please consult the [`max_flow`](@ref) section for options about flow_algorithm and capacity_matrix) -```julia +```jldoctest +julia> flow_graph = DiGraph(8) # Create a flow graph -# Create a flow-graph and a capacity matrix -flow_graph = DiGraph(8) -flow_edges = [ - (1, 2, 10), (1, 3, 5), (1, 4, 15), (2, 3, 4), (2, 5, 9), - (2, 6, 15), (3, 4, 4), (3, 6, 8), (4, 7, 16), (5, 6, 15), - (5, 8, 10), (6, 7, 15), (6, 8, 10), (7, 3, 6), (7, 8, 10) +julia> flow_edges = [ +(1, 2, 10), (1, 3, 5), (1, 4, 15), (2, 3, 4), (2, 5, 9), +(2, 6, 15), (3, 4, 4), (3, 6, 8), (4, 7, 16), (5, 6, 15), +(5, 8, 10), (6, 7, 15), (6, 8, 10), (7, 3, 6), (7, 8, 10) ] -capacity_matrix = zeros(Int, 8, 8) -for e in flow_edges + +julia> capacity_matrix = zeros(Int, 8, 8) # Create a capacity matrix + +julia> for e in flow_edges u, v, f = e add_edge!(flow_graph, u, v) capacity_matrix[u, v] = f end -# Run default multiroute_flow with an integer number of routes = 2 -f, F = multiroute_flow(flow_graph, 1, 8, capacity_matrix, routes = 2) +julia> f, F = multiroute_flow(flow_graph, 1, 8, capacity_matrix, routes = 2) # Run default multiroute_flow with an integer number of routes = 2 -# Run default multiroute_flow with a noninteger number of routes = 1.5 -f, F = multiroute_flow(flow_graph, 1, 8, capacity_matrix, routes = 1.5) +julia> f, F = multiroute_flow(flow_graph, 1, 8, capacity_matrix, routes = 1.5) # Run default multiroute_flow with a noninteger number of routes = 1.5 -# Run default multiroute_flow for all the breaking points values -points = multiroute_flow(flow_graph, 1, 8, capacity_matrix) -# Then run multiroute flow algorithm for any positive number of routes -f, F = multiroute_flow(points, 1.5) -f = multiroute_flow(points, 1.5, valueonly = true) +julia> points = multiroute_flow(flow_graph, 1, 8, capacity_matrix) # Run default multiroute_flow for all the breaking points values -# Run multiroute flow algorithm using Boykov-Kolmogorov algorithm as max_flow routine -f, F, labels = multiroute_flow(flow_graph, 1, 8, capacity_matrix, - algorithm = BoykovKolmogorovAlgorithm(), routes = 2) +julia> f, F = multiroute_flow(points, 1.5) # Then run multiroute flow algorithm for any positive number of routes + +julia> f = multiroute_flow(points, 1.5, valueonly = true) + +julia> f, F, labels = multiroute_flow(flow_graph, 1, 8, capacity_matrix, algorithm = BoykovKolmogorovAlgorithm(), routes = 2) # Run multiroute flow algorithm using Boykov-Kolmogorov algorithm as max_flow routine ``` """ - -function multiroute_flow{T<:Real, R<:Real}( - flow_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T, 2} = # edge flow capacities - DefaultCapacity(flow_graph); - flow_algorithm::AbstractFlowAlgorithm = # keyword argument for algorithm +function multiroute_flow( + flow_graph::AbstractGraph, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix = # edge flow capacities + DefaultCapacity(flow_graph); + flow_algorithm::AbstractFlowAlgorithm = # keyword argument for algorithm PushRelabelAlgorithm(), - mrf_algorithm::AbstractMultirouteFlowAlgorithm = # keyword argument for algorithm + mrf_algorithm::AbstractMultirouteFlowAlgorithm = # keyword argument for algorithm KishimotoAlgorithm(), - routes::R = 0 # keyword argument for number of routes (0 = all values) - ) - # a flow with a set of 1-disjoint pathes is a classical max-flow - (routes == 1) && - return maximum_flow(flow_graph, source, target, capacity_matrix, flow_algorithm) - - # routes > λ (connectivity) → f = 0 - λ = maximum_flow(flow_graph, source, target, DefaultCapacity(flow_graph), - algorithm = flow_algorithm)[1] - (routes > λ) && return empty_flow(capacity_matrix, flow_algorithm) - - # For other cases, capacities need to be Floats - if !(T<:AbstractFloat) - capacity_matrix = convert(AbstractArray{Float64, 2}, capacity_matrix) - end - - # Ask for all possible values (breaking points) - (routes == 0) && - return emrf(flow_graph, source, target, capacity_matrix, flow_algorithm) - - # The number of routes is a float → EMRF - (R <: AbstractFloat) && - return emrf(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) - - # Other calls - return multiroute_flow(flow_graph, source, target, capacity_matrix, - flow_algorithm, mrf_algorithm, routes) + routes::R = 0 # keyword argument for number of routes (0 = all values) + ) where R <: Real + + # a flow with a set of 1-disjoint paths is a classical max-flow + (routes == 1) && + return maximum_flow(flow_graph, source, target, capacity_matrix, flow_algorithm) + + # routes > λ (connectivity) → f = 0 + λ = maximum_flow(flow_graph, source, target, DefaultCapacity(flow_graph), + algorithm = flow_algorithm)[1] + (routes > λ) && return empty_flow(capacity_matrix, flow_algorithm) + + # For other cases, capacities need to be Floats + T = eltype(capacity_matrix) + if !(T<:AbstractFloat) + capacity_matrix = convert(AbstractMatrix{Float64}, capacity_matrix) + end + + # Ask for all possible values (breaking points) + (routes == 0) && + return emrf(flow_graph, source, target, capacity_matrix, flow_algorithm) + # The number of routes is a float → EMRF + (R <: AbstractFloat) && + return emrf(flow_graph, source, target, capacity_matrix, flow_algorithm, routes) + + # Other calls + return multiroute_flow(flow_graph, source, target, capacity_matrix, + flow_algorithm, mrf_algorithm, routes) end diff --git a/src/flow/push_relabel.jl b/src/flow/push_relabel.jl index 295c47854..803af8f98 100644 --- a/src/flow/push_relabel.jl +++ b/src/flow/push_relabel.jl @@ -1,31 +1,22 @@ -""" -Implementation of the FIFO push relabel algorithm with gap heuristic. Takes -approximately O(V^3) time. - -Maintains the following auxillary arrays: -- height -> Stores the labels of all vertices -- count -> Stores the number of vertices at each height -- excess -> Stores the difference between incoming and outgoing flow for all vertices -- active -> Stores the status of all vertices. (e(v)>0 => active[v] = true) -- Q -> The FIFO queue that stores active vertices waiting to be discharged. +@doc_str """ + push_relabel(residual_graph, source, target, capacity_matrix) -Requires arguments: +Return the maximum flow of `residual_graph` from `source` to `target` using the +FIFO push relabel algorithm with gap heuristic. -- residual_graph::DiGraph # the input graph -- source::Int # the source vertex -- target::Int # the target vertex -- capacity_matrix::AbstractArray{T,2} # edge flow capacities +### Performance +Takes approximately ``\\mathcal{O}(|V|^{3})`` time. """ - -function push_relabel{T<:Number}( - residual_graph::DiGraph, # the input graph - source::Int, # the source vertex - target::Int, # the target vertex - capacity_matrix::AbstractArray{T,2} # edge flow capacities +function push_relabel end +@traitfn function push_relabel( + residual_graph::::IsDirected, # the input graph + source::Integer, # the source vertex + target::Integer, # the target vertex + capacity_matrix::AbstractMatrix # edge flow capacities ) n = nv(residual_graph) - + T = eltype(capacity_matrix) flow_matrix = zeros(T, n, n) height = zeros(Int, n) @@ -46,7 +37,7 @@ function push_relabel{T<:Number}( sizehint!(Q, n) - for v in fadj(residual_graph, source) + for v in out_neighbors(residual_graph, source) push_flow!(residual_graph, source, v, capacity_matrix, flow_matrix, excess, height, active, Q) end @@ -56,25 +47,21 @@ function push_relabel{T<:Number}( discharge!(residual_graph, v, capacity_matrix, flow_matrix, excess, height, active, count, Q) end - return sum([flow_matrix[v,target] for v in badj(residual_graph, target) ]), flow_matrix + return sum([flow_matrix[v,target] for v in in_neighbors(residual_graph, target) ]), flow_matrix end """ -Pushes inactive nodes into the queue and activates them. - -Requires arguments: + enqueue_vertex!(Q, v, active, excess) -- Q::AbstractArray{Int,1} -- v::Int -- active::AbstractArray{Bool,1} -- excess::AbstractArray{T,1} +Push inactive node `v` into queue `Q` and activates it. Requires preallocated +`active` and `excess` vectors. """ -function enqueue_vertex!{T<:Number}( - Q::AbstractArray{Int,1}, - v::Int, # input vertex - active::AbstractArray{Bool,1}, - excess::AbstractArray{T,1} +function enqueue_vertex!( + Q::AbstractVector, + v::Integer, # input vertex + active::AbstractVector{Bool}, + excess::AbstractVector ) if !active[v] && excess[v] > 0 active[v] = true @@ -84,31 +71,23 @@ function enqueue_vertex!{T<:Number}( end """ -Pushes as much flow as possible through the given edge. - -Requires arguements: - -- residual_graph::DiGraph # the input graph -- u::Int # input from-vertex -- v::Int # input to-vetex -- capacity_matrix::AbstractArray{T,2} -- flow_matrix::AbstractArray{T,2} -- excess::AbstractArray{T,1} -- height::AbstractArray{Int,1} -- active::AbstractArray{Bool,1} -- Q::AbstractArray{Int,1} -""" + push_flow!(residual_graph, u, v, capacity_matrix, flow_matrix, excess, height, active, Q) -function push_flow!{T<:Number}( - residual_graph::DiGraph, # the input graph - u::Int, # input from-vertex - v::Int, # input to-vetex - capacity_matrix::AbstractArray{T,2}, - flow_matrix::AbstractArray{T,2}, - excess::AbstractArray{T,1}, - height::AbstractArray{Int,1}, - active::AbstractArray{Bool,1}, - Q::AbstractArray{Int,1} +Using `residual_graph` with capacities in `capacity_matrix`, push as much flow +as possible through the given edge(`u`, `v`). Requires preallocated `flow_matrix` +matrix, and `excess`, `height, `active`, and `Q` vectors. +""" +function push_flow! end +@traitfn function push_flow!( + residual_graph::::IsDirected, # the input graph + u::Integer, # input from-vertex + v::Integer, # input to-vetex + capacity_matrix::AbstractMatrix, + flow_matrix::AbstractMatrix, + excess::AbstractVector, + height::AbstractVector{Int}, + active::AbstractVector{Bool}, + Q::AbstractVector ) flow = min(excess[u], capacity_matrix[u,v] - flow_matrix[u,v]) @@ -126,28 +105,30 @@ function push_flow!{T<:Number}( end """ -Implements the gap heuristic. Relabels all vertices above a cutoff height. -Reduces the number of relabels required. + gap!(residual_graph, h, excess, height, active, count, Q) + +Implement the push-relabel gap heuristic. Relabel all vertices above a cutoff height. +Reduce the number of relabels required. Requires arguments: - residual_graph::DiGraph # the input graph - h::Int # cutoff height -- excess::AbstractArray{T,1} -- height::AbstractArray{Int,1} -- active::AbstractArray{Bool,1} -- count::AbstractArray{Int,1} -- Q::AbstractArray{Int,1} +- excess::AbstractVector +- height::AbstractVector{Int} +- active::AbstractVector{Bool} +- count::AbstractVector{Int} +- Q::AbstractVector """ - -function gap!{T<:Number}( - residual_graph::DiGraph, # the input graph +function gap! end +@traitfn function gap!( + residual_graph::::IsDirected, # the input graph h::Int, # cutoff height - excess::AbstractArray{T,1}, - height::AbstractArray{Int,1}, - active::AbstractArray{Bool,1}, - count::AbstractArray{Int,1}, - Q::AbstractArray{Int,1} # FIFO queue + excess::AbstractVector, + height::AbstractVector{Int}, + active::AbstractVector{Bool}, + count::AbstractVector{Int}, + Q::AbstractVector # FIFO queue ) n = nv(residual_graph) for v in vertices(residual_graph) @@ -161,37 +142,26 @@ function gap!{T<:Number}( end """ -Relabels a vertex with respect to its neighbors, to produce an admissable -edge. - -Requires arguments: + relabel!(residual_graph, v, capacity_matrix, flow_matrix, excess, height, active, count, Q) -- residual_graph::DiGraph # the input graph -- v::Int # input vertex to be relabeled -- capacity_matrix::AbstractArray{T,2} -- flow_matrix::AbstractArray{T,2} -- excess::AbstractArray{T,1} -- height::AbstractArray{Int,1} -- active::AbstractArray{Bool,1} -- count::AbstractArray{Int,1} -- Q::AbstractArray{Int,1} +Relabel a node `v` with respect to its neighbors to produce an admissable edge. """ - -function relabel!{T<:Number}( - residual_graph::DiGraph, # the input graph - v::Int, # input vertex to be relabeled - capacity_matrix::AbstractArray{T,2}, - flow_matrix::AbstractArray{T,2}, - excess::AbstractArray{T,1}, - height::AbstractArray{Int,1}, - active::AbstractArray{Bool,1}, - count::AbstractArray{Int,1}, - Q::AbstractArray{Int,1} +function relabel! end +@traitfn function relabel!( + residual_graph::::IsDirected, # the input graph + v::Integer, # input vertex to be relabeled + capacity_matrix::AbstractMatrix, + flow_matrix::AbstractMatrix, + excess::AbstractVector, + height::AbstractVector{Int}, + active::AbstractVector{Bool}, + count::AbstractVector{Int}, + Q::AbstractVector ) n = nv(residual_graph) count[height[v]+1] -= 1 height[v] = 2*n - for to in fadj(residual_graph, v) + for to in out_neighbors(residual_graph, v) if capacity_matrix[v,to] > flow_matrix[v,to] height[v] = min(height[v], height[to]+1) end @@ -203,33 +173,24 @@ end """ -Drains the excess flow out of a vertex. Runs the gap heuristic or relabels the -vertex if the excess remains non-zero. + discharge!(residual_graph, v, capacity_matrix, flow_matrix, excess, height, active, count, Q) -Requires arguments: - -- residual_graph::DiGraph # the input graph -- v::Int # vertex to be discharged -- capacity_matrix::AbstractArray{T,2} -- flow_matrix::AbstractArray{T,2} -- excess::AbstractArray{T,1} -- height::AbstractArray{Int,1} -- active::AbstractArray{Bool,1} -- count::AbstractArray{Int,1} -- Q::AbstractArray{Int,1} +Drain the excess flow out of node `v`. Run the gap heuristic or relabel the +vertex if the excess remains non-zero. """ -function discharge!{T<:Number}( - residual_graph::DiGraph, # the input graph - v::Int, # vertex to be discharged - capacity_matrix::AbstractArray{T,2}, - flow_matrix::AbstractArray{T,2}, - excess::AbstractArray{T,1}, - height::AbstractArray{Int,1}, - active::AbstractArray{Bool,1}, - count::AbstractArray{Int,1}, - Q::AbstractArray{Int,1} # FIFO queue +function discharge! end +@traitfn function discharge!( + residual_graph::::IsDirected, # the input graph + v::Integer, # vertex to be discharged + capacity_matrix::AbstractMatrix, + flow_matrix::AbstractMatrix, + excess::AbstractVector, + height::AbstractVector{Int}, + active::AbstractVector{Bool}, + count::AbstractVector{Int}, + Q::AbstractVector # FIFO queue ) - for to in fadj(residual_graph, v) + for to in out_neighbors(residual_graph, v) excess[v] == 0 && break push_flow!(residual_graph, v, to, capacity_matrix, flow_matrix, excess, height, active, Q) end diff --git a/src/generators/euclideangraphs.jl b/src/generators/euclideangraphs.jl index 30dd98fa0..58e12adbe 100644 --- a/src/generators/euclideangraphs.jl +++ b/src/generators/euclideangraphs.jl @@ -1,37 +1,37 @@ +@doc_str """ + euclidean_graph(N, d; seed=-1, L=1., p=2., cutoff=-1., bc=:open) + +Generate `N` uniformly distributed points in the box ``[0,L]^{d}`` +and return a Euclidean graph, a map containing the distance on each edge and +a matrix with the points' positions. """ - euclidean_graph(points::Matrix, L=1., p=2., cutoff=-1., bc=:open) +function euclidean_graph(N::Int, d::Int; + L=1., seed = -1, kws...) + rng = LightGraphs.getRNG(seed) + points = scale!(rand(rng, d, N), L) + return (euclidean_graph(points; L=L, kws...)..., points) +end -Given the `d×N` matrix `points` builds an Euclidean graph of `N` vertices -according to the following procedure. +""" + euclidean_graph(points) + +Given the `d×N` matrix `points` build an Euclidean graph of `N` vertices and +return a graph and Dict containing the distance on each edge. + +### Optional Arguments +- `L=1`: used to bound the `d` dimensional box from which points are selected. +- `p=2` +- `bc=:open` +### Implementation Notes Defining the `d`-dimensional vectors `x[i] = points[:,i]`, an edge between vertices `i` and `j` is inserted if `norm(x[i]-x[j], p) < cutoff`. In case of negative `cutoff` instead every edge is inserted. For `p=2` we have the standard Euclidean distance. Set `bc=:periodic` to impose periodic boundary conditions in the box ``[0,L]^d``. - -Returns a graph and Dict containing the distance on each edge. - - - euclidean_graph(N, d; seed = -1, L=1., p=2., cutoff=-1., bc=:open) - -Generates `N` uniformly distributed points in the box ``[0,L]^d`` -and builds and Euclidean graph. - -Returns a graph, a Dict containing the distance on each edge and a matrix with -the points' positions. """ -function euclidean_graph end - -function euclidean_graph(N::Int, d::Int; - L=1., seed = -1, kws...) - rng = LightGraphs.getRNG(seed) - points = scale!(rand(rng, d, N), L) - return (euclidean_graph(points; L=L, kws...)..., points) -end - function euclidean_graph(points::Matrix; - L=1., p=2., cutoff=-1., bc=:open) + L=1., p=2., cutoff=-1., bc=:open) d, N = size(points) g = Graph(N) weights = Dict{Edge,Float64}() diff --git a/src/generators/randgraphs.jl b/src/generators/randgraphs.jl index 558d55693..d6b12536c 100644 --- a/src/generators/randgraphs.jl +++ b/src/generators/randgraphs.jl @@ -1,65 +1,82 @@ function Graph(nv::Integer, ne::Integer; seed::Int = -1) - maxe = div(nv * (nv-1), 2) + T = eltype(nv) + maxe = div(Int(nv) * (nv-1), 2) @assert(ne <= maxe, "Maximum number of edges for this graph is $maxe") ne > 2/3 * maxe && return complement(Graph(nv, maxe-ne)) rng = getRNG(seed) g = Graph(nv) + while g.ne < ne - source = rand(rng, 1:nv) - dest = rand(rng, 1:nv) + source = rand(rng, one(T):nv) + dest = rand(rng, one(T):nv) source != dest && add_edge!(g,source,dest) end return g end function DiGraph(nv::Integer, ne::Integer; seed::Int = -1) - maxe = nv * (nv-1) + T = eltype(nv) + maxe = Int(nv) * (nv-1) @assert(ne <= maxe, "Maximum number of edges for this graph is $maxe") ne > 2/3 * maxe && return complement(DiGraph(nv, maxe-ne)) rng = getRNG(seed) g = DiGraph(nv) while g.ne < ne - source = rand(rng, 1:nv) - dest = rand(rng, 1:nv) + source = rand(rng, one(T):nv) + dest = rand(rng, one(T):nv) source != dest && add_edge!(g,source,dest) end return g end """ - erdos_renyi(n::Integer, p::Real; is_directed=false, seed=-1) - erdos_renyi(n::Integer, ne::Integer; is_directed=false, seed=-1) + erdos_renyi(n, p) -Creates an [Erdős–Rényi](http://en.wikipedia.org/wiki/Erdős–Rényi_model) +Create an [Erdős–Rényi](http://en.wikipedia.org/wiki/Erdős–Rényi_model) random graph with `n` vertices. Edges are added between pairs of vertices with -probability `p`. Undirected graphs are created by default; use -`is_directed=true` to override. +probability `p`. -Note also that Erdős–Rényi graphs may be generated quickly using `erdos_renyi(n, ne)` -or the `Graph(nv, ne)` constructor, which randomly select `ne` edges among all the potential -edges. +### Optional Arguments +- `is_directed=false`: if true, return a directed graph. +- `seed=-1`: set the RNG seed. """ function erdos_renyi(n::Integer, p::Real; is_directed=false, seed::Integer=-1) m = is_directed ? n*(n-1) : div(n*(n-1),2) if seed >= 0 # init dsfmt generator without altering GLOBAL_RNG - Base.dSFMT.dsfmt_gv_init_by_array(MersenneTwister(seed).seed+1) + Base.dSFMT.dsfmt_gv_init_by_array(MersenneTwister(seed).seed+0x01) end ne = rand(Binomial(m, p)) # sadly StatsBase doesn't support non-global RNG return is_directed ? DiGraph(n, ne, seed=seed) : Graph(n, ne, seed=seed) end +""" + erdos_renyi(n, ne) + +Create an [Erdős–Rényi](http://en.wikipedia.org/wiki/Erdős–Rényi_model) random +graph with `n` vertices and `ne` edges. + +### Optional Arguments +- `is_directed=false`: if true, return a directed graph. +- `seed=-1`: set the RNG seed. +""" function erdos_renyi(n::Integer, ne::Integer; is_directed=false, seed::Integer=-1) return is_directed ? DiGraph(n, ne, seed=seed) : Graph(n, ne, seed=seed) end -"""Creates a [Watts-Strogatz](https://en.wikipedia.org/wiki/Watts_and_Strogatz_model) +""" + 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 `β`. Undirected graphs are -created by default; use `is_directed=true` to override. +randomized per the model based on probability `β`. + +### Optional Arguments +- `is_directed=false`: if true, return a directed graph. +- `seed=-1`: set the RNG seed. """ function watts_strogatz(n::Integer, k::Integer, β::Real; is_directed=false, seed::Int = -1) @assert k < n/2 @@ -93,7 +110,7 @@ function watts_strogatz(n::Integer, k::Integer, β::Real; is_directed=false, see return g end -function _suitable(edges::Set{Edge}, potential_edges::Dict{Int, Int}) +function _suitable(edges::Set{Edge}, potential_edges::Dict{T, T}) where T<:Integer isempty(potential_edges) && return true list = keys(potential_edges) for s1 in list, s2 in list @@ -103,14 +120,14 @@ function _suitable(edges::Set{Edge}, potential_edges::Dict{Int, Int}) return false end -_try_creation(n::Int, k::Int, rng::AbstractRNG) = _try_creation(n, fill(k,n), rng) +_try_creation(n::Integer, k::Integer, rng::AbstractRNG) = _try_creation(n, fill(k,n), rng) -function _try_creation(n::Int, k::Vector{Int}, rng::AbstractRNG) +function _try_creation(n::T, k::Vector{T}, rng::AbstractRNG) where T<:Integer edges = Set{Edge}() m = 0 - stubs = zeros(Int, sum(k)) - for i=1:n - for j = 1:k[i] + stubs = zeros(T, sum(k)) + for i=one(T):n + for j = one(T):k[i] m += 1 stubs[m] = i end @@ -118,7 +135,7 @@ function _try_creation(n::Int, k::Vector{Int}, rng::AbstractRNG) # stubs = vcat([fill(i, k[i]) for i=1:n]...) # slower while !isempty(stubs) - potential_edges = Dict{Int,Int}() + potential_edges = Dict{T,T}() shuffle!(rng, stubs) for i in 1:2:length(stubs) s1,s2 = stubs[i:i+1] @@ -147,27 +164,35 @@ function _try_creation(n::Int, k::Vector{Int}, rng::AbstractRNG) end """ - barabasi_albert(n::Integer, k::Integer; is_directed::Bool = false, complete::Bool = false, seed::Int = -1) + barabasi_albert(n, k) -Creates a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) +Create a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) random graph with `n` vertices. It is grown by adding new vertices to an initial graph with `k` vertices. Each new vertex is attached with `k` edges to `k` different vertices already present in the system by preferential attachment. -Initial graphs are undirected and consist of isolated vertices by default; -use `is_directed=true` and `complete=true` for directed and complete initial graphs. +Initial graphs are undirected and consist of isolated vertices by default. + +### Optional Arguments +- `is_directed=false`: if true, return a directed graph. +- `complete=false`: if true, use a complete graph for the initial graph. +- `seed=-1`: set the RNG seed. """ barabasi_albert(n::Integer, k::Integer; keyargs...) = - barabasi_albert(n, k, k; keyargs...) +barabasi_albert(n, k, k; keyargs...) """ - barabasi_albert(n::Integer, n0::Integer, k::Integer; is_directed::Bool = false, complete::Bool = false, seed::Int = -1) + barabasi_albert(n::Integer, n0::Integer, k::Integer) -Creates a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) +Create a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) random graph with `n` vertices. It is grown by adding new vertices to an initial graph with `n0` vertices. Each new vertex is attached with `k` edges to `k` different vertices already present in the system by preferential attachment. -Initial graphs are undirected and consist of isolated vertices by default; -use `is_directed=true` and `complete=true` for directed and complete initial graphs. +Initial graphs are undirected and consist of isolated vertices by default. + +### Optional Arguments +- `is_directed=false`: if true, return a directed graph. +- `complete=false`: if true, use a complete graph for the initial graph. +- `seed=-1`: set the RNG seed. """ function barabasi_albert(n::Integer, n0::Integer, k::Integer; is_directed::Bool = false, complete::Bool = false, seed::Int = -1) if complete @@ -181,18 +206,21 @@ function barabasi_albert(n::Integer, n0::Integer, k::Integer; is_directed::Bool end """ - barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int = -1) + barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer) -Creates a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) +Create a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) random graph with `n` vertices. It is grown by adding new vertices to an initial graph `g`. Each new vertex is attached with `k` edges to `k` different vertices already present in the system by preferential attachment. + +### Optional Arguments +- `seed=-1`: set the RNG seed. """ function barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int=-1) n0 = nv(g) 1 <= k <= n0 <= n || - throw(ArgumentError("Barabási-Albert model requires 1 <= k <= n0 <= n" * - "where n0 is the number of nodes in graph g")) + throw(ArgumentError("Barabási-Albert model requires 1 <= k <= n0 <= n" * + "where n0 is the number of vertices in graph g")) n0 == n && return g # seed random number generator @@ -208,20 +236,20 @@ function barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int=-1 # expand initial graph n0 += 1 - # add edges to k existing nodes + # add edges to k existing vertices for target in sample!(collect(1:n0-1), k) add_edge!(g, n0, target) end end - # vector of weighted nodes (each node is repeated once for each adjacent edge) - weightedNodes = Vector{Int}(2*(n-n0)*k + 2*ne(g)) + # vector of weighted vertices (each node is repeated once for each adjacent edge) + weightedVs = Vector{Int}(2*(n-n0)*k + 2*ne(g)) - # initialize vector of weighted nodes + # initialize vector of weighted vertices offset = 0 for e in edges(g) - weightedNodes[offset+=1] = src(e) - weightedNodes[offset+=1] = dst(e) + weightedVs[offset+=1] = src(e) + weightedVs[offset+=1] = dst(e) end # array to record if a node is picked @@ -231,11 +259,11 @@ function barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int=-1 targets = Vector{Int}(k) for source in n0+1:n - # choose k targets from the existing nodes - # pick uniformly from weightedNodes (preferential attachement) + # choose k targets from the existing vertices + # pick uniformly from weightedVs (preferential attachement) i = 0 while i < k - target = weightedNodes[rand(1:offset)] + target = weightedVs[rand(1:offset)] if !picked[target] targets[i+=1] = target picked[target] = true @@ -246,8 +274,8 @@ function barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int=-1 for target in targets add_edge!(g, source, target) - weightedNodes[offset+=1] = source - weightedNodes[offset+=1] = target + weightedVs[offset+=1] = source + weightedVs[offset+=1] = target picked[target] = false end end @@ -256,30 +284,34 @@ function barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int=-1 end -""" - static_fitness_model{T<:Real}(m::Int, fitness::Vector{T}; seed::Int=-1) +@doc_str """ + static_fitness_model(m, fitness) + +Generate a random graph with ``|fitness|`` vertices and `m` edges, +in which the probability of the existence of ``Edge_{ij}`` is proportional +to ``fitness_i × fitness_j`. -Generates a random graph with `length(fitness)` nodes and `m` edges, -in which the probability of the existence of edge `(i, j)` is proportional -to `fitness[i]*fitness[j]`. Time complexity is O(|V| + |E| log |E|). +### Optional Arguments +- `seed=-1`: set the RNG seed. -Reference: +### Performance +Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``. -* Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution -in scale-free networks. Phys Rev Lett 87(27):278701, 2001. +### References +- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001. """ -function static_fitness_model{T<:Real}(m::Int, fitness::Vector{T}; seed::Int=-1) +function static_fitness_model(m::Integer, fitness::Vector{T}; seed::Int=-1) where T<:Real @assert(m >= 0, "invalid number of edges") n = length(fitness) m == 0 && return Graph(n) - nodes = 0 + nvs = 0 for f in fitness # sanity check for the fitness f < zero(T) && error("fitness scores must be non-negative") - f > zero(T) && (nodes += 1) + f > zero(T) && (nvs += 1) end # avoid getting into an infinite loop when too many edges are requested - max_no_of_edges = div(nodes*(nodes-1), 2) + max_no_of_edges = div(nvs*(nvs-1), 2) @assert(m <= max_no_of_edges, "too many edges requested") # calculate the cumulative fitness scores cum_fitness = cumsum(fitness) @@ -288,21 +320,37 @@ function static_fitness_model{T<:Real}(m::Int, fitness::Vector{T}; seed::Int=-1) return g end -function static_fitness_model{T<:Real,S<:Real}(m::Int, fitness_out::Vector{T}, fitness_in::Vector{S}; seed::Int=-1) +@doc_str """ + static_fitness_model(m, fitness_out, fitness_in) + +Generate a random graph with ``|fitness_out + fitness_in|`` vertices and `m` edges, +in which the probability of the existence of ``Edge_{ij}`` is proportional with +respect to ``i ∝ fitness_out`` and ``j ∝ fitness_in``. + +### Optional Arguments +- `seed=-1`: set the RNG seed. + +### Performance +Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``. + +### References +- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001. +""" +function static_fitness_model(m::Integer, fitness_out::Vector{T}, fitness_in::Vector{S}; seed::Int=-1) where T<:Real where S<:Real @assert(m >= 0, "invalid number of edges") n = length(fitness_out) @assert(length(fitness_in) == n, "fitness_in must have the same size as fitness_out") m == 0 && return DiGraph(n) # avoid getting into an infinite loop when too many edges are requested - outnodes = innodes = nodes = 0 + noutvs = ninvs = nvs = 0 @inbounds for i=1:n # sanity check for the fitness (fitness_out[i] < zero(T) || fitness_in[i] < zero(S)) && error("fitness scores must be non-negative") - fitness_out[i] > zero(T) && (outnodes += 1) - fitness_in[i] > zero(S) && (innodes += 1) - (fitness_out[i] > zero(T) && fitness_in[i] > zero(S)) && (nodes += 1) + fitness_out[i] > zero(T) && (noutvs += 1) + fitness_in[i] > zero(S) && (ninvs += 1) + (fitness_out[i] > zero(T) && fitness_in[i] > zero(S)) && (nvs += 1) end - max_no_of_edges = outnodes*innodes - nodes + max_no_of_edges = noutvs*ninvs - nvs @assert(m <= max_no_of_edges, "too many edges requested") # calculate the cumulative fitness scores cum_fitness_out = cumsum(fitness_out) @@ -312,7 +360,7 @@ function static_fitness_model{T<:Real,S<:Real}(m::Int, fitness_out::Vector{T}, f return g end -function _create_static_fitness_graph!{T<:Real,S<:Real}(g::AbstractGraph, m::Int, cum_fitness_out::Vector{T}, cum_fitness_in::Vector{S}, seed::Int) +function _create_static_fitness_graph!(g::AbstractGraph, m::Integer, cum_fitness_out::Vector{T}, cum_fitness_in::Vector{S}, seed::Int) where T<:Real where S<:Real rng = getRNG(seed) max_out = cum_fitness_out[end] max_in = cum_fitness_in[end] @@ -328,32 +376,54 @@ function _create_static_fitness_graph!{T<:Real,S<:Real}(g::AbstractGraph, m::Int end end -""" - function static_scale_free(n::Int, m::Int, α::Float64; seed::Int=-1, finite_size_correction::Bool=true) - -Generates a random graph with `n` vertices, `m` edges and expected power-law -degree distribution with exponent `α`. `finite_size_correction` determines -whether to use the finite size correction proposed by Cho et al. -This generator calls internally the `static_fitness_model function`. -Time complexity is O(|V| + |E| log |E|). +@doc_str """ + static_scale_free(n, m, α) -References: +Generate a random graph with `n` vertices, `m` edges and expected power-law +degree distribution with exponent `α`. -* Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001. +### Optional Arguments +- `seed=-1`: set the RNG seed. +- `finite_size_correction=true`: determines whether to use the finite size correction +proposed by Cho et al. -* Chung F and Lu L: Connected components in a random graph with given degree sequences. Annals of Combinatorics 6, 125-145, 2002. +### Performance +Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``. -* Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in scale-free networks under the Achlioptas process. Phys Rev Lett 103:135702, 2009. +### References +- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001. +- Chung F and Lu L: Connected components in a random graph with given degree sequences. Annals of Combinatorics 6, 125-145, 2002. +- Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in scale-free networks under the Achlioptas process. Phys Rev Lett 103:135702, 2009. """ -function static_scale_free(n::Int, m::Int, α::Float64; seed::Int=-1, finite_size_correction::Bool=true) - @assert(n >= 0, "Invalid number of nodes") +function static_scale_free(n::Integer, m::Integer, α::Real; seed::Int=-1, finite_size_correction::Bool=true) + @assert(n >= 0, "Invalid number of vertices") @assert(α >= 2, "out-degree exponent must be >= 2") fitness = _construct_fitness(n, α, finite_size_correction) static_fitness_model(m, fitness, seed=seed) end -function static_scale_free(n::Int, m::Int, α_out::Float64, α_in::Float64; seed::Int=-1, finite_size_correction::Bool=true) - @assert(n >= 0, "Invalid number of nodes") +@doc_str """ + static_scale_free(n, m, α_out, α_in) + +Generate a random graph with `n` vertices, `m` edges and expected power-law +degree distribution with exponent `α_out` for outbound edges and `α_in` for +inbound edges. + +### Optional Arguments +- `seed=-1`: set the RNG seed. +- `finite_size_correction=true`: determines whether to use the finite size correction +proposed by Cho et al. + +### Performance +Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``. + +### References +- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001. +- Chung F and Lu L: Connected components in a random graph with given degree sequences. Annals of Combinatorics 6, 125-145, 2002. +- Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in scale-free networks under the Achlioptas process. Phys Rev Lett 103:135702, 2009. +""" +function static_scale_free(n::Integer, m::Integer, α_out::Real, α_in::Float64; seed::Int=-1, finite_size_correction::Bool=true) + @assert(n >= 0, "Invalid number of vertices") @assert(α_out >= 2, "out-degree exponent must be >= 2") @assert(α_in >= 2, "in-degree exponent must be >= 2") # construct the fitness @@ -364,7 +434,7 @@ function static_scale_free(n::Int, m::Int, α_out::Float64, α_in::Float64; seed static_fitness_model(m, fitness_out, fitness_in, seed=seed) end -function _construct_fitness(n::Int, α::Float64, finite_size_correction::Bool) +function _construct_fitness(n::Integer, α::Real, finite_size_correction::Bool) α = -1/(α-1) fitness = zeros(n) j = float(n) @@ -380,18 +450,24 @@ function _construct_fitness(n::Int, α::Float64, finite_size_correction::Bool) return fitness end -doc""" - random_regular_graph(n::Int, k::Int; seed=-1) +@doc_str """ + random_regular_graph(n, k) -Creates a random undirected +Create a random undirected [regular graph](https://en.wikipedia.org/wiki/Regular_graph) with `n` vertices, each with degree `k`. -For undirected graphs, allocates an array of `nk` `Int`s, and takes -approximately $nk^2$ time. For $k > n/2$, generates a graph of degree -`n-k-1` and returns its complement. +### Optional Arguments +- `seed=-1`: set the RNG seed. + +### Performance +Time complexity is approximately ``nk^2``. + +### Implementation Notes +Allocates an array of `nk` `Int`s, and . For ``k > \\frac{n}{2}``, generates a graph of degree +``n-k-1`` and returns its complement. """ -function random_regular_graph(n::Int, k::Int; seed::Int=-1) +function random_regular_graph(n::Integer, k::Integer; seed::Int=-1) @assert(iseven(n*k), "n * k must be even") @assert(0 <= k < n, "the 0 <= k < n inequality must be satisfied") if k == 0 @@ -416,21 +492,24 @@ function random_regular_graph(n::Int, k::Int; seed::Int=-1) return g end +@doc_str """ + random_configuration_model(n, ks) -doc""" - random_configuration_model(n::Int, k::Array{Int}; seed=-1, check_graphical=false) - -Creates a random undirected graph according to the [configuration model] -(http://tuvalu.santafe.edu/~aaronc/courses/5352/fall2013/csci5352_2013_L11.pdf). -It contains `n` vertices, the vertex `i` having degree `k[i]`. +Create a random undirected graph according to the [configuration model] +(http://tuvalu.santafe.edu/~aaronc/courses/5352/fall2013/csci5352_2013_L11.pdf) +containing `n` vertices, with each node `i` having degree `k[i]`. -Defining `c = mean(k)`, it allocates an array of `nc` `Int`s, and takes -approximately $nc^2$ time. +### Optional Arguments +- `seed=-1`: set the RNG seed. +- `check_graphical=false`: if true, ensure that `k` is a graphical sequence +(see [`isgraphical`](@ref)). - -If `check_graphical=true` makes sure that `k` is a graphical sequence (see `isgraphical`). +### Performance +Time complexity is approximately ``n \\bar{k}^2``. +### Implementation Notes +Allocates an array of ``n \\bar{k}`` `Int`s. """ -function random_configuration_model(n::Int, k::Array{Int}; seed::Int=-1, check_graphical::Bool=false) +function random_configuration_model(n::Integer, k::Array{T}; seed::Int=-1, check_graphical::Bool=false) where T<:Integer @assert(n == length(k), "a degree sequence of length n has to be provided") m = sum(k) @assert(iseven(m), "sum(k) must be even") @@ -453,18 +532,21 @@ function random_configuration_model(n::Int, k::Array{Int}; seed::Int=-1, check_g return g end -doc""" - random_regular_digraph(n::Int, k::Int; dir::Symbol=:out, seed=-1) +@doc_str """ + random_regular_digraph(n, k) -Creates a random directed -[regular graph](https://en.wikipedia.org/wiki/Regular_graph) with `n` vertices, -each with degree `k`. The degree (in or out) can be -specified using `dir=:in` or `dir=:out`. The default is `dir=:out`. +Create a random directed [regular graph](https://en.wikipedia.org/wiki/Regular_graph) +with `n` vertices, each with degree `k`. + +### Optional Arguments +- `dir=:out`: the direction of the edges for degree parameter. +- `seed=-1`: set the RNG seed. -For directed graphs, allocates an $n \times n$ sparse matrix of boolean as an -adjacency matrix and uses that to generate the directed graph. +### Implementation Notes +Allocates an ``n × n`` sparse matrix of boolean as an adjacency matrix and +uses that to generate the directed graph. """ -function random_regular_digraph(n::Int, k::Int; dir::Symbol=:out, seed::Int=-1) +function random_regular_digraph(n::Integer, k::Integer; dir::Symbol=:out, seed::Int=-1) #TODO remove the function sample from StatsBase for one allowing the use # of a local rng @assert(0 <= k < n, "the 0 <= k < n inequality must be satisfied") @@ -478,8 +560,8 @@ function random_regular_digraph(n::Int, k::Int; dir::Symbol=:out, seed::Int=-1) rng = getRNG(seed) cs = collect(2:n) i = 1 - I = Array(Int, n*k) - J = Array(Int, n*k) + I = Vector{Int}(n*k) + J = Vector{Int}(n*k) V = fill(true, n*k) for r in 1:n l = (r-1)*k+1 : r*k @@ -494,23 +576,23 @@ function random_regular_digraph(n::Int, k::Int; dir::Symbol=:out, seed::Int=-1) end end -doc""" - stochastic_block_model(c::Matrix{Float64}, n::Vector{Int}; seed::Int = -1) - stochastic_block_model(cin::Float64, coff::Float64, n::Vector{Int}; seed::Int = -1) +@doc_str """ + stochastic_block_model(c, n) -Returns a Graph generated according to the Stochastic Block Model (SBM). +Return a Graph generated according to the Stochastic Block Model (SBM). `c[a,b]` : Mean number of neighbors of a vertex in block `a` belonging to block `b`. Only the upper triangular part is considered, since the lower traingular is - determined by $c[b,a] = c[a,b] * n[a]/n[b]$. + determined by ``c[b,a] = c[a,b] * \\frac{n[a]}{n[b]}``. `n[a]` : Number of vertices in block `a` -The second form samples from a SBM with `c[a,a]=cin`, and `c[a,b]=coff`. +### Optional Arguments +- `seed=-1`: set the RNG seed. -For a dynamic version of the SBM see the `StochasticBlockModel` type and +For a dynamic version of the SBM see the [`StochasticBlockModel`](@ref) type and related functions. """ -function stochastic_block_model{T<:Real}(c::Matrix{T}, n::Vector{Int}; seed::Int = -1) +function stochastic_block_model(c::Matrix{T}, n::Vector{U}; seed::Int = -1) where T<:Real where U<:Integer @assert size(c,1) == length(n) @assert size(c,2) == length(n) # init dsfmt generator without altering GLOBAL_RNG @@ -546,19 +628,20 @@ function stochastic_block_model{T<:Real}(c::Matrix{T}, n::Vector{Int}; seed::Int return g end -function stochastic_block_model{T<:Real}(cint::T, cext::T, n::Vector{Int}; seed::Int=-1) +@doc_str """ + stochastic_block_model(cint, cext, n) + +Return a Graph generated according to the Stochastic Block Model (SBM), sampling +from an SBM with ``c_{a,a}=cint``, and ``c_{a,b}=cext``. +""" +function stochastic_block_model(cint::T, cext::T, n::Vector{U}; seed::Int=-1) where T<:Real where U<:Integer K = length(n) c = [ifelse(a==b, cint, cext) for a=1:K,b=1:K] stochastic_block_model(c, n, seed=seed) end """ - type StochasticBlockModel{T<:Integer,P<:Real} - n::T - nodemap::Array{T} - affinities::Matrix{P} - rng::MersenneTwister - end + StochasticBlockModel{T,P} A type capturing the parameters of the SBM. Each vertex is assigned to a block and the probability of edge `(i,j)` @@ -568,12 +651,13 @@ The assignement is stored in nodemap and the block affinities a `k` by `k` matrix is stored in affinities. `affinities[k,l]` is the probability of an edge between any vertex in -block k and any vertex in block `l`. +block `k` and any vertex in block `l`. -We are generating the graphs by taking random `i,j in vertices(g)` and +### Implementation Notes +Graphs are generated by taking random ``i,j ∈ V`` and flipping a coin with probability `affinities[nodemap[i],nodemap[j]]`. """ -type StochasticBlockModel{T<:Integer,P<:Real} +mutable struct StochasticBlockModel{T<:Integer,P<:Real} n::T nodemap::Array{T} affinities::Matrix{P} @@ -583,11 +667,11 @@ end ==(sbm::StochasticBlockModel, other::StochasticBlockModel) = (sbm.n == other.n) && (sbm.nodemap == other.nodemap) && (sbm.affinities == other.affinities) -"""A constructor for StochasticBlockModel that uses the sizes of the blocks -and the affinity matrix. This construction implies that consecutive -vertices will be in the same blocks, except for the block boundaries. -""" -function StochasticBlockModel{T,P}(sizes::Vector{T}, affinities::Matrix{P}; seed::Int = -1) + +# A constructor for StochasticBlockModel that uses the sizes of the blocks +# and the affinity matrix. This construction implies that consecutive +# vertices will be in the same blocks, except for the block boundaries. +function StochasticBlockModel(sizes::AbstractVector, affinities::AbstractMatrix; seed::Int = -1) csum = cumsum(sizes) j = 1 nodemap = zeros(Int, csum[end]) @@ -601,28 +685,32 @@ function StochasticBlockModel{T,P}(sizes::Vector{T}, affinities::Matrix{P}; seed end -"""Produce the sbm affinity matrix where the external probabilities are the same -the internal probabilities and sizes differ by blocks. +### TODO: This documentation needs work. sbromberger 20170326 +""" + sbmaffinity(internalp, externalp, sizes) + +Produce the sbm affinity matrix with internal probabilities `internalp` +and external probabilities `externalp`. """ -function sbmaffinity(internalp::Vector{Float64}, externalp::Float64, sizes::Vector{Int}) +function sbmaffinity(internalp::Vector{T}, externalp::Real, sizes::Vector{U}) where T<:Real where U<:Integer numblocks = length(sizes) numblocks == length(internalp) || error("Inconsistent input dimensions: internalp, sizes") B = diagm(internalp) + externalp*(ones(numblocks, numblocks)-I) return B end -function StochasticBlockModel(internalp::Float64, - externalp::Float64, - size::Int, - numblocks::Int; +function StochasticBlockModel(internalp::Real, + externalp::Real, + size::Integer, + numblocks::Integer; seed::Int = -1) sizes = fill(size, numblocks) B = sbmaffinity(fill(internalp, numblocks), externalp, sizes) StochasticBlockModel(sizes, B, seed=seed) end -function StochasticBlockModel(internalp::Vector{Float64}, externalp::Float64 - , sizes::Vector{Int}; seed::Int = -1) +function StochasticBlockModel(internalp::Vector{T}, externalp::Real + , sizes::Vector{U}; seed::Int = -1) where T<:Real where U<:Integer B = sbmaffinity(internalp, externalp, sizes) return StochasticBlockModel(sizes, B, seed=seed) end @@ -630,21 +718,25 @@ end const biclique = ones(2,2) - eye(2) -"""Construct the affinity matrix for a near bipartite SBM. -between is the affinity between the two parts of each bipartite community -intra is the probability of an edge within the parts of the partitions. +#TODO: this documentation needs work. sbromberger 20170326 +@doc_str """ + nearbipartiteaffinity(sizes, between, intra) + +Construct the affinity matrix for a near bipartite SBM. +`between` is the affinity between the two parts of each bipartite community. +`intra` is the probability of an edge within the parts of the partitions. -This is a specific type of SBM with k/2 blocks each with two halves. +This is a specific type of SBM with ``\\frac{k}{2} blocks each with two halves. Each half is connected as a random bipartite graph with probability `intra` The blocks are connected with probability `between`. """ -function nearbipartiteaffinity(sizes::Vector{Int}, between::Float64, intra::Float64) +function nearbipartiteaffinity(sizes::Vector{T}, between::Real, intra::Real) where T<:Integer numblocks = div(length(sizes), 2) return kron(between*eye(numblocks), biclique) + eye(2numblocks)*intra end -"""Return a generator for edges from a stochastic block model near-bipartite graph.""" -function nearbipartiteaffinity(sizes::Vector{Int}, between::Float64, inter::Float64, noise::Real) +#Return a generator for edges from a stochastic block model near-bipartite graph. +function nearbipartiteaffinity(sizes::Vector{T}, between::Real, inter::Real, noise::Real) where T<:Integer B = nearbipartiteaffinity(sizes, between, inter) + noise # info("Affinities are:\n$B")#, file=stderr) return B @@ -655,49 +747,65 @@ function nearbipartiteSBM(sizes, between, inter, noise; seed::Int = -1) end -"""Generates a stream of random pairs in 1:n""" -function random_pair(rng::AbstractRNG, n::Int) - while true - produce( rand(rng, 1:n), rand(rng, 1:n) ) +""" + random_pair(rng, n) + +Generate a stream of random pairs in `1:n` using random number generator `RNG`. +""" +function random_pair(rng::AbstractRNG, n::Integer) + f(ch) = begin + while true + put!(ch, Edge(rand(rng, 1:n), rand(rng, 1:n))) + end end + return f end """ - make_edgestream(sbm::StochasticBlockModel) + make_edgestream(sbm) -Take an infinite sample from the sbm. -Pass to `Graph(nvg, neg, edgestream)` to get a Graph object. +Take an infinite sample from the Stochastic Block Model `sbm`. +Pass to `Graph(nvg, neg, edgestream)` to get a Graph object based on `sbm`. """ function make_edgestream(sbm::StochasticBlockModel) - pairs = @task random_pair(sbm.rng, sbm.n) - for (i,j) in pairs - if i == j - continue - end - p = sbm.affinities[sbm.nodemap[i], sbm.nodemap[j]] - if rand(sbm.rng) < p - produce(i, j) + pairs = Channel(random_pair(sbm.rng, sbm.n), ctype=Edge, csize=32) + edges(ch) = begin + for e in pairs + i, j = Tuple(e) + if i == j + continue + end + p = sbm.affinities[sbm.nodemap[i], sbm.nodemap[j]] + if rand(sbm.rng) < p + put!(ch, e) + end end end + return Channel(edges, ctype=Edge, csize=32) end -function Graph(nvg::Int, neg::Int, edgestream::Task) +function Graph(nvg::Integer, neg::Integer, edgestream::Channel) g = Graph(nvg) # println(g) - for (i,j) in edgestream + for e in edgestream # print("$count, $i,$j\n") - add_edge!(g, Edge(i, j)) + add_edge!(g, e) ne(g) >= neg && break end # println(g) return g end -Graph(nvg::Int, neg::Int, sbm::StochasticBlockModel) = - Graph(nvg, neg, @task make_edgestream(sbm)) +Graph(nvg::Integer, neg::Integer, sbm::StochasticBlockModel) = + Graph(nvg, neg, make_edgestream(sbm)) -"""counts the number of edges that go between each block""" +#TODO: this documentation needs work. sbromberger 20170326 +""" + blockcounts(sbm, A) + +Count the number of edges that go between each block. +""" function blockcounts(sbm::StochasticBlockModel, A::AbstractMatrix) # info("making Q") I = collect(1:sbm.n) diff --git a/src/generators/smallgraphs.jl b/src/generators/smallgraphs.jl index 00b74c77b..d1865e2a6 100644 --- a/src/generators/smallgraphs.jl +++ b/src/generators/smallgraphs.jl @@ -2,7 +2,7 @@ # STATIC SMALL GRAPHS ##################### -function _make_simple_undirected_graph{T<:Integer}(n::T, edgelist::Vector{Tuple{T,T}}) +function _make_simple_undirected_graph(n::T, edgelist::Vector{Tuple{T,T}}) where T<:Integer g = Graph(n) for (s,d) in edgelist add_edge!(g, Edge(s,d)) @@ -10,7 +10,7 @@ function _make_simple_undirected_graph{T<:Integer}(n::T, edgelist::Vector{Tuple{ return g end -function _make_simple_directed_graph{T<:Integer}(n::T, edgelist::Vector{Tuple{T,T}}) +function _make_simple_directed_graph(n::T, edgelist::Vector{Tuple{T,T}}) where T<:Integer g = DiGraph(n) for (s,d) in edgelist add_edge!(g, Edge(s,d)) @@ -19,10 +19,10 @@ function _make_simple_directed_graph{T<:Integer}(n::T, edgelist::Vector{Tuple{T, end doc""" - smallgraph(s::Symbol) - smallgraph(s::AbstractString) + smallgraph(s) + smallgraph(s) -Creates a small graph of type `s`. Admissible values for `s` are: +Create a small graph of type `s`. Admissible values for `s` are: | `s` | graph type | |:------------------------|:---------------------------------| @@ -48,7 +48,6 @@ Creates a small graph of type `s`. Admissible values for `s` are: | :truncatedtetrahedron | A skeleton of the [truncated tetrahedron graph](https://en.wikipedia.org/wiki/Truncated_tetrahedron). | | :truncatedtetrahedron_dir | A skeleton of the [truncated tetrahedron digraph](https://en.wikipedia.org/wiki/Truncated_tetrahedron). | | :tutte | A [Tutte graph](https://en.wikipedia.org/wiki/Tutte_graph). | - """ function smallgraph(s::Symbol) graphmap = Dict( @@ -93,25 +92,25 @@ end DiamondGraph() = - _make_simple_undirected_graph(4, [(1,2), (1,3), (2,3), (2,4), (3,4)]) +_make_simple_undirected_graph(4, [(1,2), (1,3), (2,3), (2,4), (3,4)]) BullGraph() = - _make_simple_undirected_graph(5, [(1,2), (1,3), (2,3), (2,4), (3,5)]) +_make_simple_undirected_graph(5, [(1,2), (1,3), (2,3), (2,4), (3,5)]) function ChvatalGraph() e = [ - (1, 2), (1, 5), (1, 7), (1, 10), - (2, 3), (2, 6), (2, 8), - (3, 4), (3, 7), (3, 9), - (4, 5), (4, 8), (4, 10), - (5, 6), (5, 9), - (6, 11), (6, 12), - (7, 11), (7, 12), - (8, 9), (8, 12), - (9, 11), - (10, 11), (10, 12) + (1, 2), (1, 5), (1, 7), (1, 10), + (2, 3), (2, 6), (2, 8), + (3, 4), (3, 7), (3, 9), + (4, 5), (4, 8), (4, 10), + (5, 6), (5, 9), + (6, 11), (6, 12), + (7, 11), (7, 12), + (8, 9), (8, 12), + (9, 11), + (10, 11), (10, 12) ] return _make_simple_undirected_graph(12,e) end @@ -119,12 +118,12 @@ end function CubicalGraph() e = [ - (1, 2), (1, 4), (1, 5), - (2, 3), (2, 8), - (3, 4), (3, 7), - (4, 6), (5, 6), (5, 8), - (6, 7), - (7, 8) + (1, 2), (1, 4), (1, 5), + (2, 3), (2, 8), + (3, 4), (3, 7), + (4, 6), (5, 6), (5, 8), + (6, 7), + (7, 8) ] return _make_simple_undirected_graph(8,e) end @@ -132,25 +131,25 @@ end function DesarguesGraph() e = [ - (1, 2), (1, 6), (1, 20), - (2, 3), (2, 17), - (3, 4), (3, 12), - (4, 5), (4, 15), - (5, 6), (5, 10), - (6, 7), - (7, 8), (7, 16), - (8, 9), (8, 19), - (9, 10), (9, 14), - (10, 11), - (11, 12), (11, 20), - (12, 13), - (13, 14), (13, 18), - (14, 15), - (15, 16), - (16, 17), - (17, 18), - (18, 19), - (19, 20) + (1, 2), (1, 6), (1, 20), + (2, 3), (2, 17), + (3, 4), (3, 12), + (4, 5), (4, 15), + (5, 6), (5, 10), + (6, 7), + (7, 8), (7, 16), + (8, 9), (8, 19), + (9, 10), (9, 14), + (10, 11), + (11, 12), (11, 20), + (12, 13), + (13, 14), (13, 18), + (14, 15), + (15, 16), + (16, 17), + (17, 18), + (18, 19), + (19, 20) ] return _make_simple_undirected_graph(20,e) end @@ -158,25 +157,25 @@ end function DodecahedralGraph() e = [ - (1, 2), (1, 11), (1, 20), - (2, 3), (2, 9), - (3, 4), (3, 7), - (4, 5), (4, 20), - (5, 6), (5, 18), - (6, 7), (6, 16), - (7, 8), - (8, 9), (8, 15), - (9, 10), - (10, 11), (10, 14), - (11, 12), - (12, 13), (12, 19), - (13, 14), (13, 17), - (14, 15), - (15, 16), - (16, 17), - (17, 18), - (18, 19), - (19, 20) + (1, 2), (1, 11), (1, 20), + (2, 3), (2, 9), + (3, 4), (3, 7), + (4, 5), (4, 20), + (5, 6), (5, 18), + (6, 7), (6, 16), + (7, 8), + (8, 9), (8, 15), + (9, 10), + (10, 11), (10, 14), + (11, 12), + (12, 13), (12, 19), + (13, 14), (13, 17), + (14, 15), + (15, 16), + (16, 17), + (17, 18), + (18, 19), + (19, 20) ] return _make_simple_undirected_graph(20,e) end @@ -184,16 +183,16 @@ end function FruchtGraph() e = [ - (1, 2), (1, 7), (1, 8), - (2, 3), (2, 8), - (3, 4), (3, 9), - (4, 5), (4, 10), - (5, 6), (5, 10), - (6, 7), (6, 11), - (7, 11), - (8, 12), - (9, 10), (9, 12), - (11, 12) + (1, 2), (1, 7), (1, 8), + (2, 3), (2, 8), + (3, 4), (3, 9), + (4, 5), (4, 10), + (5, 6), (5, 10), + (6, 7), (6, 11), + (7, 11), + (8, 12), + (9, 10), (9, 12), + (11, 12) ] return _make_simple_undirected_graph(20,e) end @@ -235,15 +234,15 @@ end function IcosahedralGraph() e = [ - (1, 2), (1, 6), (1, 8), (1, 9), (1, 12), - (2, 3), (2, 6), (2, 7), (2, 9), - (3, 4), (3, 7), (3, 9), (3, 10), - (4, 5), (4, 7), (4, 10), (4, 11), - (5, 6), (5, 7), (5, 11), (5, 12), - (6, 7), (6, 12), - (8, 9), (8, 10), (8, 11), (8, 12), - (9, 10), - (10, 11), (11, 12) + (1, 2), (1, 6), (1, 8), (1, 9), (1, 12), + (2, 3), (2, 6), (2, 7), (2, 9), + (3, 4), (3, 7), (3, 9), (3, 10), + (4, 5), (4, 7), (4, 10), (4, 11), + (5, 6), (5, 7), (5, 11), (5, 12), + (6, 7), (6, 12), + (8, 9), (8, 10), (8, 11), (8, 12), + (9, 10), + (10, 11), (11, 12) ] return _make_simple_undirected_graph(12, e) end @@ -251,15 +250,15 @@ end function KrackhardtKiteGraph() e = [ - (1, 2), (1, 3), (1, 4), (1, 6), - (2, 4), (2, 5), (2, 7), - (3, 4), (3, 6), - (4, 5), (4, 6), (4, 7), - (5, 7), - (6, 7), (6, 8), - (7, 8), - (8, 9), - (9, 10) + (1, 2), (1, 3), (1, 4), (1, 6), + (2, 4), (2, 5), (2, 7), + (3, 4), (3, 6), + (4, 5), (4, 6), (4, 7), + (5, 7), + (6, 7), (6, 8), + (7, 8), + (8, 9), + (9, 10) ] return _make_simple_undirected_graph(10,e) end @@ -267,21 +266,21 @@ end function MoebiusKantorGraph() e = [ - (1, 2), (1, 6), (1, 16), - (2, 3), (2, 13), - (3, 4), (3, 8), - (4, 5), (4, 15), - (5, 6), (5, 10), - (6, 7), - (7, 8), (7, 12), - (8, 9), - (9, 10), (9, 14), - (10, 11), - (11, 12), (11, 16), - (12, 13), - (13, 14), - (14, 15), - (15, 16) + (1, 2), (1, 6), (1, 16), + (2, 3), (2, 13), + (3, 4), (3, 8), + (4, 5), (4, 15), + (5, 6), (5, 10), + (6, 7), + (7, 8), (7, 12), + (8, 9), + (9, 10), (9, 14), + (10, 11), + (11, 12), (11, 16), + (12, 13), + (13, 14), + (14, 15), + (15, 16) ] return _make_simple_undirected_graph(16,e) end @@ -289,11 +288,11 @@ end function OctahedralGraph() e = [ - (1, 2), (1, 3), (1, 4), (1, 5), - (2, 3), (2, 4), (2, 6), - (3, 5), (3, 6), - (4, 5), (4, 6), - (5, 6) + (1, 2), (1, 3), (1, 4), (1, 5), + (2, 3), (2, 4), (2, 6), + (3, 5), (3, 6), + (4, 5), (4, 6), + (5, 6) ] return _make_simple_undirected_graph(6,e) end @@ -301,23 +300,23 @@ end function PappusGraph() e = [ - (1, 2), (1, 6), (1, 18), - (2, 3), (2, 9), - (3, 4), (3, 14), - (4, 5), (4, 11), - (5, 6), (5, 16), - (6, 7), - (7, 8), (7, 12), - (8, 9), (8, 15), - (9, 10), - (10, 11), (10, 17), - (11, 12), - (12, 13), - (13, 14), (13, 18), - (14, 15), - (15, 16), - (16, 17), - (17, 18) + (1, 2), (1, 6), (1, 18), + (2, 3), (2, 9), + (3, 4), (3, 14), + (4, 5), (4, 11), + (5, 6), (5, 16), + (6, 7), + (7, 8), (7, 12), + (8, 9), (8, 15), + (9, 10), + (10, 11), (10, 17), + (11, 12), + (12, 13), + (13, 14), (13, 18), + (14, 15), + (15, 16), + (16, 17), + (17, 18) ] return _make_simple_undirected_graph(18,e) end @@ -325,59 +324,59 @@ end function PetersenGraph() e = [ - (1, 2), (1, 5), (1, 6), - (2, 3), (2, 7), - (3, 4), (3, 8), - (4, 5), (4, 9), - (5, 10), - (6, 8), (6, 9), - (7, 9), (7, 10), - (8, 10) + (1, 2), (1, 5), (1, 6), + (2, 3), (2, 7), + (3, 4), (3, 8), + (4, 5), (4, 9), + (5, 10), + (6, 8), (6, 9), + (7, 9), (7, 10), + (8, 10) ] return _make_simple_undirected_graph(10,e) end function SedgewickMazeGraph() e = [ - (1, 3), - (1, 6), (1, 8), - (2, 8), - (3, 7), - (4, 5), (4, 6), - (5, 6), (5, 7), (5, 8) + (1, 3), + (1, 6), (1, 8), + (2, 8), + (3, 7), + (4, 5), (4, 6), + (5, 6), (5, 7), (5, 8) ] return _make_simple_undirected_graph(8,e) end TetrahedralGraph() = - _make_simple_undirected_graph(4, [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]) +_make_simple_undirected_graph(4, [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]) function TruncatedCubeGraph() e = [ - (1, 2), (1, 3), (1, 5), - (2, 12), (2, 15), - (3, 4), (3, 5), - (4, 7), (4, 9), - (5, 6), - (6, 17), (6, 19), - (7, 8), (7, 9), - (8, 11), (8, 13), - (9, 10), - (10, 18), (10, 21), - (11, 12), (11, 13), - (12, 15), (13, 14), - (14, 22), (14, 23), - (15, 16), - (16, 20), (16, 24), - (17, 18), (17, 19), - (18, 21), - (19, 20), - (20, 24), - (21, 22), - (22, 23), - (23, 24) + (1, 2), (1, 3), (1, 5), + (2, 12), (2, 15), + (3, 4), (3, 5), + (4, 7), (4, 9), + (5, 6), + (6, 17), (6, 19), + (7, 8), (7, 9), + (8, 11), (8, 13), + (9, 10), + (10, 18), (10, 21), + (11, 12), (11, 13), + (12, 15), (13, 14), + (14, 22), (14, 23), + (15, 16), + (16, 20), (16, 24), + (17, 18), (17, 19), + (18, 21), + (19, 20), + (20, 24), + (21, 22), + (22, 23), + (23, 24) ] return _make_simple_undirected_graph(24,e) end @@ -385,17 +384,17 @@ end function TruncatedTetrahedronGraph() e = [ - (1, 2),(1, 3),(1, 10), - (2, 3),(2, 7), - (3, 4), - (4, 5),(4, 12), - (5, 6),(5, 12), - (6, 7),(6, 8), - (7, 8), - (8, 9), - (9, 10),(9, 11), - (10, 11), - (11, 12) + (1, 2),(1, 3),(1, 10), + (2, 3),(2, 7), + (3, 4), + (4, 5),(4, 12), + (5, 6),(5, 12), + (6, 7),(6, 8), + (7, 8), + (8, 9), + (9, 10),(9, 11), + (10, 11), + (11, 12) ] return _make_simple_undirected_graph(12,e) end @@ -403,17 +402,17 @@ end function TruncatedTetrahedronDiGraph() e = [ - (1, 2),(1, 3),(1, 10), - (2, 3),(2, 7), - (3, 4), - (4, 5),(4, 12), - (5, 6),(5, 12), - (6, 7),(6, 8), - (7, 8), - (8, 9), - (9, 10),(9, 11), - (10, 11), - (11, 12) + (1, 2),(1, 3),(1, 10), + (2, 3),(2, 7), + (3, 4), + (4, 5),(4, 12), + (5, 6),(5, 12), + (6, 7),(6, 8), + (7, 8), + (8, 9), + (9, 10),(9, 11), + (10, 11), + (11, 12) ] return _make_simple_directed_graph(12,e) end diff --git a/src/generators/staticgraphs.jl b/src/generators/staticgraphs.jl index 5bb241a59..40bebcaf8 100644 --- a/src/generators/staticgraphs.jl +++ b/src/generators/staticgraphs.jl @@ -1,8 +1,11 @@ # Parts of this code were taken / derived from NetworkX. See LICENSE for # licensing details. -"""Creates a complete graph with `n` vertices. A complete graph has edges -connecting each pair of vertices. +""" + CompleteGraph(n) + +Create an undirected [complete graph](https://en.wikipedia.org/wiki/Complete_graph) +with `n` vertices. """ function CompleteGraph(n::Integer) g = Graph(n) @@ -15,8 +18,11 @@ function CompleteGraph(n::Integer) end -"""Creates a complete bipartite graph with `n1+n2` vertices. It has edges -connecting each pair of vertices in the two sets. +@doc_str """ + CompleteBipartiteGraph(n1, n2) + +Create an undirected [complete bipartite graph](https://en.wikipedia.org/wiki/Complete_bipartite_graph) +with `n1 + n2` vertices. """ function CompleteBipartiteGraph(n1::Integer, n2::Integer) g = Graph(n1+n2) @@ -26,8 +32,11 @@ function CompleteBipartiteGraph(n1::Integer, n2::Integer) return g end -"""Creates a complete digraph with `n` vertices. A complete digraph has edges -connecting each pair of vertices (both an ingoing and outgoing edge). +""" + CompleteDiGraph(n) + +Create a directed [complete graph](https://en.wikipedia.org/wiki/Complete_graph) +with `n` vertices. """ function CompleteDiGraph(n::Integer) g = DiGraph(n) @@ -39,8 +48,11 @@ function CompleteDiGraph(n::Integer) return g end -"""Creates a star graph with `n` vertices. A star graph has a central vertex -with edges to each other vertex. +""" + StarGraph(n) + +Create an undirected [star graph](https://en.wikipedia.org/wiki/Star_(graph_theory)) +with `n` vertices. """ function StarGraph(n::Integer) g = Graph(n) @@ -50,8 +62,11 @@ function StarGraph(n::Integer) return g end -"""Creates a star digraph with `n` vertices. A star digraph has a central -vertex with directed edges to every other vertex. +""" + StarDiGraph(n) + +Create a directed [star graph](https://en.wikipedia.org/wiki/Star_(graph_theory)) +with `n` vertices. """ function StarDiGraph(n::Integer) g = DiGraph(n) @@ -61,8 +76,12 @@ function StarDiGraph(n::Integer) return g end -"""Creates a path graph with `n` vertices. A path graph connects each -successive vertex by a single edge.""" +""" + PathGraph(n) + +Create an undirected [path graph](https://en.wikipedia.org/wiki/Path_graph) +with `n` vertices. +""" function PathGraph(n::Integer) g = Graph(n) for i = 2:n @@ -71,8 +90,12 @@ function PathGraph(n::Integer) return g end -"""Creates a path digraph with `n` vertices. A path graph connects each -successive vertex by a single directed edge.""" +""" + PathDiGraph(n) + +Creates a directed [path graph](https://en.wikipedia.org/wiki/Path_graph) +with `n` vertices. +""" function PathDiGraph(n::Integer) g = DiGraph(n) for i = 2:n @@ -81,7 +104,11 @@ function PathDiGraph(n::Integer) return g end -"""Creates a cycle graph with `n` vertices. A cycle graph is a closed path graph. +""" + CycleGraph(n) + +Create an undirected [cycle graph](https://en.wikipedia.org/wiki/Cycle_graph) +with `n` vertices. """ function CycleGraph(n::Integer) g = Graph(n) @@ -92,7 +119,11 @@ function CycleGraph(n::Integer) return g end -"""Creates a cycle digraph with `n` vertices. A cycle digraph is a closed path digraph. +""" + CycleDiGraph(n) + +Create a directed [cycle graph](https://en.wikipedia.org/wiki/Cycle_graph) +with `n` vertices. """ function CycleDiGraph(n::Integer) g = DiGraph(n) @@ -104,8 +135,11 @@ function CycleDiGraph(n::Integer) end -"""Creates a wheel graph with `n` vertices. A wheel graph is a star graph with -the outer vertices connected via a closed path graph. +""" + WheelGraph(n) + +Create an undirected [wheel graph](https://en.wikipedia.org/wiki/Wheel_graph) +with `n` vertices. """ function WheelGraph(n::Integer) g = StarGraph(n) @@ -118,8 +152,11 @@ function WheelGraph(n::Integer) return g end -"""Creates a wheel digraph with `n` vertices. A wheel graph is a star digraph -with the outer vertices connected via a closed path graph. +""" + WheelDiGraph(n) + +Create a directed [wheel graph](https://en.wikipedia.org/wiki/Wheel_graph) +with `n` vertices. """ function WheelDiGraph(n::Integer) g = StarDiGraph(n) @@ -132,25 +169,41 @@ function WheelDiGraph(n::Integer) return g end -""" - Grid{T<:Integer}(dims::AbstractVector{T}; periodic=false) +@doc_str """ + Grid(dims; periodic=false) -Creates a `d`-dimensional cubic lattice, with `d=length(dims)` and length `dims[i]` in dimension `i`. -If `periodic=true` the resulting lattice will have periodic boundary condition in each dimension. +Create a ``|dims|``-dimensional cubic lattice, with length `dims[i]` +in dimension `i`. + +### Optional Arguments +- `periodic=false`: If true, the resulting lattice will have periodic boundary +condition in each dimension. """ -function Grid{T<:Integer}(dims::AbstractVector{T}; periodic=false) - func = periodic ? CycleGraph : PathGraph - g = func(dims[1]) - for d in dims[2:end] - g = cartesian_product(func(d), g) +function Grid(dims::AbstractVector; periodic=false) + if periodic + g = CycleGraph(dims[1]) + for d in dims[2:end] + g = cartesian_product(CycleGraph(d), g) + end + else + g = PathGraph(dims[1]) + for d in dims[2:end] + g = cartesian_product(PathGraph(d), g) + end end return g end -"""create a binary tree with k-levels vertices are numbered 1:2^levels-1""" -function BinaryTree(levels::Int) - g = Graph(2^levels-1) - for i in 0:levels-2 +""" + BinaryTree(k::Integer) + +Create a [binary tree](https://en.wikipedia.org/wiki/Binary_tree) +of depth `k`. +""" + +function BinaryTree(k::Integer) + g = Graph(Int(2^k-1)) + for i in 0:k-2 for j in 2^i:2^(i+1)-1 add_edge!(g, j, 2j) add_edge!(g, j, 2j+1) @@ -159,19 +212,32 @@ function BinaryTree(levels::Int) return g end -"""create a double complete binary tree with k-levels -used as an example for spectral clustering by Guattery and Miller 1998.""" -function DoubleBinaryTree(levels::Int) - gl = BinaryTree(levels) - gr = BinaryTree(levels) +""" + BinaryTree(k::Integer) + +Create a double complete binary tree with `k` levels. + +### References +- Used as an example for spectral clustering by Guattery and Miller 1998. +""" +function DoubleBinaryTree(k::Integer) + gl = BinaryTree(k) + gr = BinaryTree(k) g = blkdiag(gl, gr) add_edge!(g,1, nv(gl)+1) return g end -"""The Roach Graph from Guattery and Miller 1998""" -function RoachGraph(k::Int) +""" + RoachGraph(k) + +Create a Roach Graph of size `k`. + +### References +- Guattery and Miller 1998 +""" +function RoachGraph(k::Integer) dipole = CompleteGraph(2) nopole = Graph(2) antannae = crosspath(k, nopole) @@ -183,7 +249,11 @@ function RoachGraph(k::Int) end -"""This function generates `n` connected k-cliques """ +""" + CliqueGraph(k, n) + +Create a graph consisting of `n` connected `k`-cliques. +""" function CliqueGraph(k::Integer, n::Integer) g = Graph(k*n) for c=1:n diff --git a/src/graph.jl b/src/graph.jl deleted file mode 100644 index 25c0c3141..000000000 --- a/src/graph.jl +++ /dev/null @@ -1,139 +0,0 @@ -function show(io::IO, g::Graph) - if nv(g) == 0 - print(io, "empty undirected graph") - else - print(io, "{$(nv(g)), $(ne(g))} undirected graph") - end -end - -function Graph(n::Int) - fadjlist = Vector{Vector{Int}}() - sizehint!(fadjlist,n) - for i = 1:n - # sizehint!(i_s, n/4) - # sizehint!(o_s, n/4) - push!(fadjlist, Vector{Int}()) - end - return Graph(1:n, 0, fadjlist) -end - -Graph() = Graph(0) - -function Graph{T<:Real}(adjmx::AbstractMatrix{T}) - dima,dimb = size(adjmx) - isequal(dima,dimb) || error("Adjacency / distance matrices must be square") - issymmetric(adjmx) || error("Adjacency / distance matrices must be symmetric") - - g = Graph(dima) - for i in find(triu(adjmx)) - ind = ind2sub((dima,dimb),i) - add_edge!(g,ind...) - end - return g -end - -function Graph(g::DiGraph) - gnv = nv(g) - - edgect = 0 - newfadj = deepcopy(g.fadjlist) - for i in 1:gnv - for j in badj(g,i) - if (_insert_and_dedup!(newfadj[i], j)) - edgect += 2 # this is a new edge only in badjlist - else - edgect += 1 # this is an existing edge - we already have it - if i == j - edgect += 1 # need to count self loops - end - end - end - end - iseven(edgect) || throw(AssertionError("invalid edgect in graph creation - please file bug report")) - return Graph(vertices(g), edgect ÷ 2, newfadj) -end - -"""Returns the backwards adjacency list of a graph. -For each vertex the Array of `dst` for each edge eminating from that vertex. - -NOTE: returns a reference, not a copy. Do not modify result. -""" -badj(g::Graph) = fadj(g) -badj(g::Graph, v::Int) = fadj(g, v) - - -"""Returns the adjacency list of a graph. -For each vertex the Array of `dst` for each edge eminating from that vertex. - -NOTE: returns a reference, not a copy. Do not modify result. -""" -adj(g::Graph) = fadj(g) -adj(g::Graph, v::Int) = fadj(g, v) - -function copy(g::Graph) - return Graph(g.vertices, g.ne, deepcopy(g.fadjlist)) -end - -==(g::Graph, h::Graph) = - vertices(g) == vertices(h) && - ne(g) == ne(h) && - fadj(g) == fadj(h) - - -"Returns `true` if `g` is a `DiGraph`." -is_directed(g::Graph) = false - -function has_edge(g::Graph, e::Edge) - u, v = Tuple(e) - u > nv(g) || v > nv(g) && return false - if degree(g,u) > degree(g,v) - u, v = v, u - end - return length(searchsorted(fadj(g,u), v)) > 0 -end - -function add_edge!(g::Graph, e::Edge) - - s, d = Tuple(e) - (s in vertices(g) && d in vertices(g)) || return false - inserted = _insert_and_dedup!(g.fadjlist[s], d) - if inserted - g.ne += 1 - end - if s != d - inserted = _insert_and_dedup!(g.fadjlist[d], s) - end - return inserted -end - -function rem_edge!(g::Graph, e::Edge) - i = searchsorted(g.fadjlist[src(e)], dst(e)) - length(i) > 0 || return false # edge not in graph - i = i[1] - deleteat!(g.fadjlist[src(e)], i) - if src(e) != dst(e) # not a self loop - i = searchsorted(g.fadjlist[dst(e)], src(e))[1] - deleteat!(g.fadjlist[dst(e)], i) - end - g.ne -= 1 - return true # edge successfully removed -end - - -"""Add a new vertex to the graph `g`.""" -function add_vertex!(g::Graph) - g.vertices = 1:nv(g)+1 - push!(g.fadjlist, Vector{Int}()) - - return true -end - - -"""Return the number of edges (both ingoing and outgoing) from the vertex `v`.""" -degree(g::Graph, v::Int) = indegree(g,v) - -doc"""Density is defined as the ratio of the number of actual edges to the -number of possible edges. This is $|v| |v-1|$ for directed graphs and -$(|v| |v-1|) / 2$ for undirected graphs. -""" -density(g::Graph) = (2*ne(g)) / (nv(g) * (nv(g)-1)) diff --git a/src/graphtypes/simplegraphs/SimpleGraphs.jl b/src/graphtypes/simplegraphs/SimpleGraphs.jl new file mode 100644 index 000000000..3cc25d862 --- /dev/null +++ b/src/graphtypes/simplegraphs/SimpleGraphs.jl @@ -0,0 +1,152 @@ +module SimpleGraphs + +import Base: + eltype, show, ==, Pair, Tuple, copy, length, start, next, done, issubset, zero + +import LightGraphs: + _NI, _insert_and_dedup!, AbstractGraph, AbstractEdge, AbstractEdgeIter, + src, dst, edgetype, nv, ne, vertices, edges, is_directed, + add_vertex!, add_edge!, rem_vertex!, rem_edge!, + has_vertex, has_edge, in_neighbors, out_neighbors, + + indegree, outdegree, degree, has_self_loops, num_self_loops + +export AbstractSimpleGraph, AbstractSimpleDiGraph, AbstractSimpleEdge, + SimpleEdge, SimpleGraph, SimpleGraphEdge, + SimpleDiGraph, SimpleDiGraphEdge + + +""" + AbstractSimpleGraph + +An abstract type representing a simple graph structure. +AbstractSimpleGraphs must have the following elements: +- vertices::UnitRange{Integer} +- fadjlist::Vector{Vector{Integer}} +- ne::Integer +""" +abstract type AbstractSimpleGraph <: AbstractGraph end + +function show(io::IO, g::AbstractSimpleGraph) + if is_directed(g) + dir = "directed" + else + dir = "undirected" + end + if nv(g) == 0 + print(io, "empty $dir simple $(eltype(g)) graph") + else + print(io, "{$(nv(g)), $(ne(g))} $dir simple $(eltype(g)) graph") + end +end + +nv(g::AbstractSimpleGraph) = eltype(g)(length(fadj(g))) +vertices(g::AbstractSimpleGraph) = one(eltype(g)):nv(g) + + +edges(g::AbstractSimpleGraph) = SimpleEdgeIter(g) + + +fadj(g::AbstractSimpleGraph) = g.fadjlist +fadj(g::AbstractSimpleGraph, v::Integer) = g.fadjlist[v] + + +badj(x...) = _NI("badj") + +has_edge(g::AbstractSimpleGraph, u::Integer, v::Integer) = has_edge(g, edgetype(g)(u,v)) + +function add_edge!(g::AbstractSimpleGraph, u::Integer, v::Integer) + T = eltype(g) + add_edge!(g, edgetype(g)(T(u),T(v))) +end + +in_neighbors(g::AbstractSimpleGraph, v::Integer) = badj(g,v) +out_neighbors(g::AbstractSimpleGraph, v::Integer) = fadj(g,v) + +function issubset(g::T, h::T) where T<:AbstractSimpleGraph + (gmin, gmax) = extrema(vertices(g)) + (hmin, hmax) = extrema(vertices(h)) + return (hmin <= gmin <= gmax <= hmax) && issubset(edges(g), edges(h)) +end + +has_vertex(g::AbstractSimpleGraph, v::Integer) = v in vertices(g) + +ne(g::AbstractSimpleGraph) = g.ne + +function rem_edge!(g::AbstractSimpleGraph, u::Integer, v::Integer) + T = eltype(g) + rem_edge!(g, edgetype(g)(T(u), T(v))) +end + +@doc_str """ + rem_vertex!(g, v) + +Remove the vertex `v` from graph `g`. Return false if removal fails +(e.g., if vertex is not in the graph); true otherwise. + +### Performance +Time complexity is ``\\mathcal{O}(k^2)``, where ``k`` is the max of the degrees +of vertex ``v`` and vertex ``|V|``. + +### Implementation Notes +This operation has to be performed carefully if one keeps external +data structures indexed by edges or vertices in the graph, since +internally the removal is performed swapping the vertices `v` and ``|V|``, +and removing the last vertex ``|V|`` from the graph. After removal the +vertices in `g` will be indexed by ``1:|V|-1``. +""" +function rem_vertex!(g::AbstractSimpleGraph, v::Integer) + v in vertices(g) || return false + n = nv(g) + + # remove the in_edges from v + srcs = copy(in_neighbors(g, v)) + for s in srcs + rem_edge!(g, edgetype(g)(s, v)) + end + # remove the in_edges from the last vertex + neigs = copy(in_neighbors(g, n)) + for s in neigs + rem_edge!(g, edgetype(g)(s, n)) + end + if v != n + # add the edges from n back to v + for s in neigs + add_edge!(g, edgetype(g)(s, v)) + end + end + + if is_directed(g) + # remove the out_edges from v + dsts = copy(out_neighbors(g, v)) + for d in dsts + rem_edge!(g, edgetype(g)(v, d)) + end + # remove the out_edges from the last vertex + neigs = copy(out_neighbors(g, n)) + for d in neigs + rem_edge!(g, edgetype(g)(n, d)) + end + if v != n + # add the out_edges back to v + for d in neigs + add_edge!(g, edgetype(g)(v, d)) + end + end + end + + pop!(g.fadjlist) + if is_directed(g) + pop!(g.badjlist) + end + return true +end + +zero(g::T) where T<:AbstractSimpleGraph = T() + +include("simpleedge.jl") +include("simpledigraph.jl") +include("simplegraph.jl") +include("simpleedgeiter.jl") + +end # module diff --git a/src/graphtypes/simplegraphs/simpledigraph.jl b/src/graphtypes/simplegraphs/simpledigraph.jl new file mode 100644 index 000000000..f86de9443 --- /dev/null +++ b/src/graphtypes/simplegraphs/simpledigraph.jl @@ -0,0 +1,151 @@ +const SimpleDiGraphEdge = SimpleEdge + +""" + SimpleDiGraph{T} + +A type representing a directed graph. +""" +mutable struct SimpleDiGraph{T<:Integer} <: AbstractSimpleGraph + ne::Int + fadjlist::Vector{Vector{T}} # [src]: (dst, dst, dst) + badjlist::Vector{Vector{T}} # [dst]: (src, src, src) +end + + +eltype(x::SimpleDiGraph{T}) where T = T + +# DiGraph{UInt8}(6), DiGraph{Int16}(7), DiGraph{Int8}() +function (::Type{SimpleDiGraph{T}})(n::Integer = 0) where T<:Integer + fadjlist = Vector{Vector{T}}() + badjlist = Vector{Vector{T}}() + for _ = one(T):n + push!(badjlist, Vector{T}()) + push!(fadjlist, Vector{T}()) + end + return SimpleDiGraph(0, fadjlist, badjlist) +end + +# DiGraph() +SimpleDiGraph() = SimpleDiGraph{Int}() + +# DiGraph(6), DiGraph(0x5) +SimpleDiGraph(n::T) where T<:Integer = SimpleDiGraph{T}(n) + +# SimpleDiGraph(UInt8) +SimpleDiGraph(::Type{T}) where T<:Integer = SimpleDiGraph{T}(zero(T)) + +# sparse adjacency matrix constructor: DiGraph(adjmx) +function (::Type{SimpleDiGraph{T}})(adjmx::SparseMatrixCSC{U}) where T<:Integer where U<:Real + dima, dimb = size(adjmx) + isequal(dima,dimb) || error("Adjacency / distance matrices must be square") + + g = SimpleDiGraph(T(dima)) + maxc = length(adjmx.colptr) + for c = 1:(maxc-1) + for rind = adjmx.colptr[c]:adjmx.colptr[c+1]-1 + isnz = (adjmx.nzval[rind] != zero(U)) + if isnz + r = adjmx.rowval[rind] + add_edge!(g,r,c) + end + end + end + return g +end + +# dense adjacency matrix constructor: DiGraph{UInt8}(adjmx) +function (::Type{SimpleDiGraph{T}})(adjmx::AbstractMatrix) where T<:Integer + dima,dimb = size(adjmx) + isequal(dima,dimb) || error("Adjacency / distance matrices must be square") + + g = SimpleDiGraph(T(dima)) + for i in find(adjmx) + ind = ind2sub((dima,dimb),i) + add_edge!(g,ind...) + end + return g +end + +# DiGraph(adjmx) +SimpleDiGraph(adjmx::AbstractMatrix) = SimpleDiGraph{Int}(adjmx) + +# converts DiGraph{Int} to DiGraph{Int32} +function (::Type{SimpleDiGraph{T}})(g::SimpleDiGraph) where T<:Integer + h_fadj = [Vector{T}(x) for x in fadj(g)] + h_badj = [Vector{T}(x) for x in badj(g)] + return SimpleDiGraph(ne(g), h_fadj, h_badj) +end + + +# constructor from abstract graph: DiGraph(graph) +function SimpleDiGraph(g::AbstractSimpleGraph) + h = SimpleDiGraph(nv(g)) + h.ne = ne(g) * 2 - num_self_loops(g) + h.fadjlist = deepcopy(fadj(g)) + h.badjlist = deepcopy(badj(g)) + return h +end + +edgetype(::SimpleDiGraph{T}) where T<: Integer = SimpleGraphEdge{T} + + +badj(g::SimpleDiGraph) = g.badjlist +badj(g::SimpleDiGraph, v::Integer) = badj(g)[v] + + +copy(g::SimpleDiGraph{T}) where T<:Integer = +SimpleDiGraph{T}(g.ne, deepcopy(g.fadjlist), deepcopy(g.badjlist)) + + +==(g::SimpleDiGraph, h::SimpleDiGraph) = +vertices(g) == vertices(h) && +ne(g) == ne(h) && +fadj(g) == fadj(h) && +badj(g) == badj(h) + +is_directed(g::SimpleDiGraph) = true +is_directed(::Type{SimpleDiGraph}) = true +is_directed(::Type{SimpleDiGraph{T}}) where T = true + +function add_edge!(g::SimpleDiGraph, e::SimpleDiGraphEdge) + T = eltype(g) + s, d = T.(Tuple(e)) + (s in vertices(g) && d in vertices(g)) || return false + inserted = _insert_and_dedup!(g.fadjlist[s], d) + if inserted + g.ne += 1 + end + return inserted && _insert_and_dedup!(g.badjlist[d], s) +end + + +function rem_edge!(g::SimpleDiGraph, e::SimpleDiGraphEdge) + has_edge(g,e) || return false + i = searchsorted(g.fadjlist[src(e)], dst(e))[1] + deleteat!(g.fadjlist[src(e)], i) + i = searchsorted(g.badjlist[dst(e)], src(e))[1] + deleteat!(g.badjlist[dst(e)], i) + g.ne -= 1 + return true +end + + +function add_vertex!(g::SimpleDiGraph) + T = eltype(g) + (nv(g) + one(T) <= nv(g)) && return false # test for overflow + push!(g.badjlist, Vector{T}()) + push!(g.fadjlist, Vector{T}()) + + return true +end + + +function has_edge(g::SimpleDiGraph, e::SimpleDiGraphEdge) + u, v = Tuple(e) + u > nv(g) || v > nv(g) && return false + if degree(g,u) < degree(g,v) + return length(searchsorted(fadj(g,u), v)) > 0 + else + return length(searchsorted(badj(g,v), u)) > 0 + end +end diff --git a/src/graphtypes/simplegraphs/simpleedge.jl b/src/graphtypes/simplegraphs/simpleedge.jl new file mode 100644 index 000000000..cb29aec21 --- /dev/null +++ b/src/graphtypes/simplegraphs/simpleedge.jl @@ -0,0 +1,31 @@ +import Base: Pair, Tuple, show, == +import LightGraphs: AbstractEdge, src, dst, reverse + +abstract type AbstractSimpleEdge <: AbstractEdge end + +struct SimpleEdge{T<:Integer} <: AbstractSimpleEdge + src::T + dst::T +end + +SimpleEdge(t::Tuple) = SimpleEdge(t[1], t[2]) +SimpleEdge(p::Pair) = SimpleEdge(p.first, p.second) + +eltype(e::T) where T<:AbstractSimpleEdge= eltype(src(e)) + +# Accessors +src(e::AbstractSimpleEdge) = e.src +dst(e::AbstractSimpleEdge) = e.dst + +# I/O +show(io::IO, e::AbstractSimpleEdge) = print(io, "Edge $(e.src) => $(e.dst)") + +# Conversions +Pair(e::AbstractSimpleEdge) = Pair(src(e), dst(e)) +Tuple(e::AbstractSimpleEdge) = (src(e), dst(e)) + +(::Type{SimpleEdge{T}}){T<:Integer}(e::AbstractSimpleEdge) = SimpleEdge{T}(T(e.src), T(e.dst)) + +# Convenience functions +reverse(e::T) where T<:AbstractSimpleEdge = T(dst(e), src(e)) +==(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) = (src(e1) == src(e2) && dst(e1) == dst(e2)) diff --git a/src/graphtypes/simplegraphs/simpleedgeiter.jl b/src/graphtypes/simplegraphs/simpleedgeiter.jl new file mode 100644 index 000000000..e7a2e3e23 --- /dev/null +++ b/src/graphtypes/simplegraphs/simpleedgeiter.jl @@ -0,0 +1,80 @@ +mutable struct SimpleEdgeIter{T<:Integer} <: AbstractEdgeIter + m::Int + adj::Vector{Vector{T}} + directed::Bool +end + +struct SimpleEdgeIterState{T<:Integer} + s::T # src vertex + di::Int # index into adj of dest vertex + fin::Bool +end + + +eltype(::Type{SimpleEdgeIter{T}}) where T<:Integer = SimpleEdge + +SimpleEdgeIter(g::SimpleGraph) = SimpleEdgeIter(ne(g), g.fadjlist, false) +SimpleEdgeIter(g::SimpleDiGraph) = SimpleEdgeIter(ne(g), g.fadjlist, true) + +function _next( + eit::SimpleEdgeIter{T}, + state::SimpleEdgeIterState{T} = SimpleEdgeIterState(one(T),1,false), + first::Bool = true) where T <: Integer + s = state.s + di = state.di + if !first + di += 1 + end + fin = state.fin + while s <= length(eit.adj) + arr = eit.adj[s] + while di <= length(arr) + if eit.directed || s <= arr[di] + return SimpleEdgeIterState(s, di, fin) + end + di += 1 + end + s += one(T) + di = 1 + end + fin = true + return SimpleEdgeIterState(s, di, fin) +end + +start(eit::SimpleEdgeIter) = _next(eit) +done(eit::SimpleEdgeIter, state::SimpleEdgeIterState) = state.fin +length(eit::SimpleEdgeIter) = eit.m + +function next(eit::SimpleEdgeIter, state::SimpleEdgeIterState) + edge = SimpleEdge(state.s, eit.adj[state.s][state.di]) + return(edge, _next(eit, state, false)) +end + +function _isequal(e1::SimpleEdgeIter, e2) + for e in e2 + s, d = Tuple(e) + found = length(searchsorted(e1.adj[s], d)) > 0 + if !e1.directed + found = found || length(searchsorted(e1.adj[d],s)) > 0 + end + !found && return false + end + return true +end +==(e1::SimpleEdgeIter, e2::AbstractVector{SimpleEdge}) = _isequal(e1, e2) +==(e1::AbstractVector{SimpleEdge}, e2::SimpleEdgeIter) = _isequal(e2, e1) +==(e1::SimpleEdgeIter, e2::Set{SimpleEdge}) = _isequal(e1, e2) +==(e1::Set{SimpleEdge}, e2::SimpleEdgeIter) = _isequal(e2, e1) + + +function ==(e1::SimpleEdgeIter, e2::SimpleEdgeIter) + length(e1.adj) == length(e2.adj) || return false + e1.directed == e2.directed || return false + for i in 1:length(e1.adj) + e1.adj[i] == e2.adj[i] || return false + end + return true +end + +show(io::IO, eit::SimpleEdgeIter) = write(io, "SimpleEdgeIter $(eit.m)") +show(io::IO, s::SimpleEdgeIterState) = write(io, "SimpleEdgeIterState [$(s.s), $(s.di), $(s.fin)]") diff --git a/src/graphtypes/simplegraphs/simplegraph.jl b/src/graphtypes/simplegraphs/simplegraph.jl new file mode 100644 index 000000000..e064a6fef --- /dev/null +++ b/src/graphtypes/simplegraphs/simplegraph.jl @@ -0,0 +1,171 @@ +const SimpleGraphEdge = SimpleEdge + +""" + SimpleGraph{T} + +A type representing an undirected graph. +""" +mutable struct SimpleGraph{T<:Integer} <: AbstractSimpleGraph + ne::Int + fadjlist::Vector{Vector{T}} # [src]: (dst, dst, dst) +end + +eltype(x::SimpleGraph{T}) where T<:Integer = T + +# Graph{UInt8}(6), Graph{Int16}(7), Graph{UInt8}() +function (::Type{SimpleGraph{T}})(n::Integer = 0) where T<:Integer + fadjlist = Vector{Vector{T}}() + sizehint!(fadjlist, n) + for _ = one(T):n + push!(fadjlist, Vector{T}()) + end + vertices = one(T):T(n) + return SimpleGraph{T}(0, fadjlist) +end + +# Graph() +SimpleGraph() = SimpleGraph{Int}() + +# Graph(6), Graph(0x5) +SimpleGraph(n::T) where T<:Integer = SimpleGraph{T}(n) + +# SimpleGraph(UInt8) +SimpleGraph(::Type{T}) where T<:Integer = SimpleGraph{T}(zero(T)) + +# Graph{UInt8}(adjmx) +function (::Type{SimpleGraph{T}})(adjmx::AbstractMatrix) where T<:Integer + dima,dimb = size(adjmx) + isequal(dima,dimb) || error("Adjacency / distance matrices must be square") + issymmetric(adjmx) || error("Adjacency / distance matrices must be symmetric") + + g = SimpleGraph(T(dima)) + for i in find(triu(adjmx)) + ind = ind2sub((dima,dimb),i) + add_edge!(g,ind...) + end + return g +end + +# converts Graph{Int} to Graph{Int32} +function (::Type{SimpleGraph{T}})(g::SimpleGraph) where T<:Integer + h_fadj = [Vector{T}(x) for x in fadj(g)] + return SimpleGraph(ne(g), h_fadj) +end + + +# Graph(adjmx) +SimpleGraph(adjmx::AbstractMatrix) = SimpleGraph{Int}(adjmx) + +# Graph(digraph) +function SimpleGraph(g::SimpleDiGraph) + gnv = nv(g) + edgect = 0 + newfadj = deepcopy(g.fadjlist) + for i in vertices(g) + for j in badj(g,i) + if (_insert_and_dedup!(newfadj[i], j)) + edgect += 2 # this is a new edge only in badjlist + else + edgect += 1 # this is an existing edge - we already have it + if i == j + edgect += 1 # need to count self loops + end + end + end + end + iseven(edgect) || throw(AssertionError("invalid edgect in graph creation - please file bug report")) + return SimpleGraph(edgect ÷ 2, newfadj) +end + +edgetype(::SimpleGraph{T}) where T<:Integer = SimpleGraphEdge{T} + +""" + badj(g::SimpleGraph[, v::Integer]) + +Return the backwards adjacency list of a graph. If `v` is specified, +return only the adjacency list for that vertex. + +###Implementation Notes +Returns a reference, not a copy. Do not modify result. +""" +badj(g::SimpleGraph) = fadj(g) +badj(g::SimpleGraph, v::Integer) = fadj(g, v) + + +""" + adj(g[, v]) + +Return the adjacency list of a graph. If `v` is specified, return only the +adjacency list for that vertex. + +### Implementation Notes +Returns a reference, not a copy. Do not modify result. +""" +adj(g::SimpleGraph) = fadj(g) +adj(g::SimpleGraph, v::Integer) = fadj(g, v) + +copy(g::SimpleGraph) = SimpleGraph(g.ne, deepcopy(g.fadjlist)) + +==(g::SimpleGraph, h::SimpleGraph) = +vertices(g) == vertices(h) && +ne(g) == ne(h) && +fadj(g) == fadj(h) + + +""" + is_directed(g) + +Return `true` if `g` is a directed graph. +""" +is_directed(::Type{SimpleGraph}) = false +is_directed(::Type{SimpleGraph{T}}) where T = false +is_directed(g::SimpleGraph) = false + +function has_edge(g::SimpleGraph, e::SimpleGraphEdge) + u, v = Tuple(e) + u > nv(g) || v > nv(g) && return false + if degree(g,u) > degree(g,v) + u, v = v, u + end + return length(searchsorted(fadj(g,u), v)) > 0 +end + +function add_edge!(g::SimpleGraph, e::SimpleGraphEdge) + T = eltype(g) + s, d = T.(Tuple(e)) + (s in vertices(g) && d in vertices(g)) || return false + inserted = _insert_and_dedup!(g.fadjlist[s], d) + if inserted + g.ne += 1 + end + if s != d + inserted = _insert_and_dedup!(g.fadjlist[d], s) + end + return inserted +end + +function rem_edge!(g::SimpleGraph, e::SimpleGraphEdge) + i = searchsorted(g.fadjlist[src(e)], dst(e)) + length(i) > 0 || return false # edge not in graph + i = i[1] + deleteat!(g.fadjlist[src(e)], i) + if src(e) != dst(e) # not a self loop + i = searchsorted(g.fadjlist[dst(e)], src(e))[1] + deleteat!(g.fadjlist[dst(e)], i) + end + g.ne -= 1 + return true # edge successfully removed +end + + +""" + add_vertex!(g) + +Add a new vertex to the graph `g`. Return `true` if addition was successful. +""" +function add_vertex!(g::SimpleGraph) + T = eltype(g) + (nv(g) + one(T) <= nv(g)) && return false # test for overflow + push!(g.fadjlist, Vector{T}()) + return true +end diff --git a/src/interface.jl b/src/interface.jl new file mode 100644 index 000000000..96d215a61 --- /dev/null +++ b/src/interface.jl @@ -0,0 +1,188 @@ +# This file contans the common interface for LightGraphs. + +_NI(m) = error("Not implemented: $m") + +""" + AbstractEdge + +An absract type representing a single edge between two vertices of a graph. +""" +abstract type AbstractEdge end + +""" + AbstractEdgeIter + +An abstract type representing an edge iterator. +""" +abstract type AbstractEdgeIter end + +""" + AbstractGraph + +An abstract type representing a graph. +""" +abstract type AbstractGraph end + + +@traitdef IsDirected{G<:AbstractGraph} +@traitimpl IsDirected{G} <- is_directed(G) + + +# +# Interface for AbstractEdges +# + +""" + src(e) + +Return the source vertex of edge `e`. +""" +src(e::AbstractEdge) = _NI("src") + +""" + dst(e) + +Return the destination vertex of edge `e`. +""" +dst(e::AbstractEdge) = _NI("dst") + +Pair(e::AbstractEdge) = _NI("Pair") +Tuple(e::AbstractEdge) = _NI("Tuple") + +""" + reverse(e) + +Create a new edge from `e` with source and destination vertices reversed. +""" +reverse(e::AbstractEdge) = _NI("reverse") + +==(e1::AbstractEdge, e2::AbstractEdge) = _NI("==") + + +# +# Interface for AbstractGraphs +# +""" + edgetype(g) + +Return the type of graph `g`'s edge +""" +edgetype(g::AbstractGraph) = _NI("edgetype") + +""" + eltype(g) + +Return the type of the graph's vertices (must be <: Integer) +""" +eltype(g::AbstractGraph) = _NI("eltype") + +""" + nv(g) + +Return the number of vertices in `g`. +""" +nv(g::AbstractGraph) = _NI("nv") + +""" + ne(g) + +Return the number of edges in `g`. +""" +ne(g::AbstractGraph) = _NI("ne") + +""" + vertices(g) + +Return (an iterator to or collection of) the vertices of a graph. + +### Implementation Notes +A returned iterator is valid for one pass over the edges, and +is invalidated by changes to `g`. + +""" +vertices(g::AbstractGraph) = _NI("vertices") + +""" + edges(g) +Return (an iterator to or collection of) the edges of a graph. + +### Implementation Notes +A returned iterator is valid for one pass over the edges, and +is invalidated by changes to `g`. +""" +edges(x...) = _NI("edges") + +is_directed(x...) = _NI("is_directed") +is_directed{T}(::Type{T}) = _NI("is_directed") +""" + add_vertex!(g) + +Add a new vertex to the graph `g`. +Return true if the vertex was added successfully, false otherwise. +""" +add_vertex!(x...) = _NI("add_vertex!") + +""" + add_edge!(g, e) + +Add a new edge `e` to `g`. Return false if add fails +(e.g., if vertices are not in the graph, or edge already exists), true otherwise. +""" +add_edge!(x...) = _NI("add_edge!") + +""" + rem_vertex!(g) + +Remove the vertex `v` from graph `g`. Return false if removal fails +(e.g., if vertex is not in the graph), true otherwise. +""" +rem_vertex!(x...) = _NI("rem_vertex!") + +""" + rem_edge!(g, e) + +Remove the edge `e` from `g`. Return false if edge removal fails +(e.g., if edge does not exist), true otherwise. +""" +rem_edge!(x...) = _NI("rem_edge!") + +""" + has_vertex(g, v) + +Return true if `v` is a vertex of `g`. +""" +has_vertex(x...) = _NI("has_vertex") + +""" + has_edge(g, e) + +Return true if the graph `g` has an edge `e`. +""" +has_edge(x...) = _NI("has_edge") + +""" + in_neighbors(g, v) + +Return a list of all neighbors connected to vertex `v` by an incoming edge. + +### Implementation Notes +Returns a reference, not a copy. Do not modify result. +""" +in_neighbors(x...) = _NI("in_neighbors") + +""" + out_neighbors(g, v) + +Return a list of all neighbors connected to vertex `v` by an outgoing edge. + +# Implementation Notes +Returns a reference, not a copy. Do not modify result. +""" +out_neighbors(x...) = _NI("out_neighbors") + +""" + zero(g) + +Return a zero-vertex, zero-edge version of the same type of graph as `g`. +""" +zero(x...) = _NI("zero") diff --git a/src/linalg/graphmatrices.jl b/src/linalg/graphmatrices.jl index c569229de..cf24eb783 100644 --- a/src/linalg/graphmatrices.jl +++ b/src/linalg/graphmatrices.jl @@ -1,6 +1,7 @@ __precompile__(true) -@doc "A package for using the type system to check types of graph matrices." -> LinAlg -import Base: convert, sparse, size, scale, diag, eltype, ndims, ==, *, .*, issymmetric, A_mul_B!, length, Diagonal + + +import Base: convert, sparse, size, diag, eltype, ndims, ==, *, .*, issymmetric, A_mul_B!, length, Diagonal export convert, SparseMatrix, GraphMatrix, @@ -25,180 +26,170 @@ export convert, -typealias SparseMatrix{T} SparseMatrixCSC{T,Int64} +const SparseMatrix{T} = SparseMatrixCSC{T,Int64} + +""" + GraphMatrix{T} + +An abstract type to allow opertions on any type of graph matrix +""" +abstract type GraphMatrix{T} end -@doc "An abstract type to allow opertions on any type of graph matrix" -> -abstract GraphMatrix{T} +""" + Adjacency{T} -@doc "The core Adjacency matrix structure. Keeps the vertex degrees around. +The core Adjacency matrix structure. Keeps the vertex degrees around. Subtypes are used to represent the different normalizations of the adjacency matrix. Laplacian and its subtypes are used for the different Laplacian matrices. -Adjacency(lapl::Laplacian) provide a generic function for getting the +Adjacency(lapl::Laplacian) provides a generic function for getting the adjacency matrix of a Laplacian matrix. If your subtype of Laplacian does not provide an field A for the Adjacency instance, then attach another method to this function to provide an Adjacency{T} representation of the Laplacian. The Adjacency matrix here -is the final subtype that corresponds to this type of Laplacian" -> -abstract Adjacency{T} <: GraphMatrix{T} -abstract Laplacian{T} <: GraphMatrix +is the final subtype that corresponds to this type of Laplacian +""" +abstract type Adjacency{T} <: GraphMatrix{T} end +abstract type Laplacian{T} <: GraphMatrix{T} end -@doc "Combinatorial Adjacency matrix is the standard adjacency matrix from math" -> -type CombinatorialAdjacency{T,S,V} <: Adjacency{T} +""" + CombinatorialAdjacency{T,S,V} + +The standard adjacency matrix. +""" +struct CombinatorialAdjacency{T,S,V} <: Adjacency{T} A::S D::V end -function CombinatorialAdjacency{T}(A::SparseMatrix{T}) +function CombinatorialAdjacency(A::SparseMatrix{T}) where T D = vec(sum(A,1)) return CombinatorialAdjacency{T,SparseMatrix{T},typeof(D)}(A,D) end -@doc "Normalized Adjacency matrix is \$\\hat{A} = D^{-1/2} A D^{-1/2}\$. +@doc_str """ + NormalizedAdjacency{T} + +The normalized adjacency matrix is ``\\hat{A} = D^{-1/2} A D^{-1/2}``. If A is symmetric, then the normalized adjacency is also symmetric -with real eigenvalues bounded by [-1, 1]." -> -type NormalizedAdjacency{T} <: Adjacency{T} +with real eigenvalues bounded by [-1, 1]. +""" +struct NormalizedAdjacency{T} <: Adjacency{T} A::CombinatorialAdjacency{T} scalefactor::Vector{T} - - function NormalizedAdjacency(adjmat::CombinatorialAdjacency) - sf = adjmat.D.^(-1/2) - return new(adjmat, sf) - end end -function NormalizedAdjacency{T}(adjmat::CombinatorialAdjacency{T}) - return NormalizedAdjacency{T}(adjmat) +function NormalizedAdjacency(adjmat::CombinatorialAdjacency) + sf = adjmat.D.^(-1/2) + return NormalizedAdjacency(adjmat, sf) end -@doc "Transition matrix for the random walk." -> -type StochasticAdjacency{T} <: Adjacency{T} +""" + StochasticAdjacency{T} + +A transition matrix for the random walk. +""" +struct StochasticAdjacency{T} <: Adjacency{T} A::CombinatorialAdjacency{T} scalefactor::Vector{T} - function StochasticAdjacency(adjmat::CombinatorialAdjacency) - sf = adjmat.D.^(-1) - return new(adjmat, sf) - end end -function StochasticAdjacency{T}(adjmat::CombinatorialAdjacency{T}) - return StochasticAdjacency{T}(adjmat) +function StochasticAdjacency(adjmat::CombinatorialAdjacency) + sf = adjmat.D.^(-1) + return StochasticAdjacency(adjmat, sf) end -@doc "The matrix whos action is to average over each neighborhood." -> -type AveragingAdjacency{T} <: Adjacency{T} + +""" + AveragingAdjacency{T} + +The matrix whose action is to average over each neighborhood. +""" +struct AveragingAdjacency{T} <: Adjacency{T} A::CombinatorialAdjacency{T} scalefactor::Vector{T} - - function AveragingAdjacency(adjmat::CombinatorialAdjacency) - sf = adjmat.D.^(-1) - return new(adjmat, sf) - end end -function AveragingAdjacency{T}(adjmat::CombinatorialAdjacency{T}) - return AveragingAdjacency{T}(adjmat) +function AveragingAdjacency(adjmat::CombinatorialAdjacency) + sf = adjmat.D.^(-1) + return AveragingAdjacency(adjmat, sf) end -perron(adjmat::NormalizedAdjacency) = sqrt(adjmat.A.D)/norm(sqrt(adjmat.A.D)) +perron(adjmat::NormalizedAdjacency) = sqrt.(adjmat.A.D)/norm(sqrt.(adjmat.A.D)) -type PunchedAdjacency{T} <: Adjacency{T} +struct PunchedAdjacency{T} <: Adjacency{T} A::NormalizedAdjacency{T} perron::Vector{T} - - function PunchedAdjacency(adjmat::CombinatorialAdjacency) - perron=sqrt(adjmat.D)/norm(sqrt(adjmat.D)) - return new(NormalizedAdjacency(adjmat), perron) - end end - -function PunchedAdjacency{T}(adjmat::CombinatorialAdjacency{T}) - return PunchedAdjacency{T}(adjmat) +function PunchedAdjacency(adjmat::CombinatorialAdjacency) + perron=sqrt.(adjmat.D)/norm(sqrt.(adjmat.D)) + return PunchedAdjacency(NormalizedAdjacency(adjmat), perron) end + perron(m::PunchedAdjacency) = m.perron -@doc "Noop: a type to represent don't do anything. -The purpose is to help write more general code for the different scaled GraphMatrix types." -> -type Noop -end +""" + Noop -function .*(::Noop, x::Any) - return x -end +A type that represents no action. -function Diagonal(::Noop) - return Noop() -end +### Implementation Notes +- The purpose of `Noop` is to help write more general code for the +different scaled GraphMatrix types. +""" +struct Noop end +Base.broadcast(::typeof(*), ::Noop, x) = x -function A_mul_B!(Y, A::Noop, B) - return copy!(Y, B) -end +Diagonal(::Noop) = Noop() -function ==(g::GraphMatrix, h::GraphMatrix) - if typeof(g) != typeof(h) - return false - end - if g.A == h.A - return true - end -end +==(g::GraphMatrix, h::GraphMatrix) = typeof(g) == typeof(h) && (g.A == h.A) -@doc "postscalefactor(M)*M.A*prescalefactor(M) == M " -> -function postscalefactor(::Adjacency) - return Noop() -end -function postscalefactor(adjmat::NormalizedAdjacency) - return adjmat.scalefactor -end -function postscalefactor(adjmat::AveragingAdjacency) - return adjmat.scalefactor -end +postscalefactor(::Adjacency)= Noop() +postscalefactor(adjmat::NormalizedAdjacency) = adjmat.scalefactor -@doc "postscalefactor(M)*M.A*prescalefactor(M) == M " -> -function prescalefactor(::Adjacency) - return Noop() -end -function prescalefactor(adjmat::NormalizedAdjacency) - return adjmat.scalefactor -end -function prescalefactor(adjmat::StochasticAdjacency) - return adjmat.scalefactor -end +postscalefactor(adjmat::AveragingAdjacency) = adjmat.scalefactor + +prescalefactor(::Adjacency) = Noop() + +prescalefactor(adjmat::NormalizedAdjacency) = adjmat.scalefactor +prescalefactor(adjmat::StochasticAdjacency) = adjmat.scalefactor -@doc "Combinatorial Laplacian L = D-A" -> -type CombinatorialLaplacian{T} <: Laplacian{T} + +struct CombinatorialLaplacian{T} <: Laplacian{T} A::CombinatorialAdjacency{T} end -@doc "Normalized Laplacian is \$\\hat{L} = I - D^{-1/2} A D^{-1/2}\$. +@doc_str """ + NormalizedLaplacian{T} + +The normalized Laplacian is ``\\hat{L} = I - D^{-1/2} A D^{-1/2}``. If A is symmetric, then the normalized Laplacian is also symmetric -with positive eigenvalues bounded by 2." -> -type NormalizedLaplacian{T} <: Laplacian{T} +with positive eigenvalues bounded by 2. +""" +struct NormalizedLaplacian{T} <: Laplacian{T} A::NormalizedAdjacency{T} end -@doc "Laplacian version of the StochasticAdjacency matrix." -> -type StochasticLaplacian{T} <: Laplacian{T} +""" + StochasticLaplacian{T} + +Laplacian version of the StochasticAdjacency matrix. +""" +struct StochasticLaplacian{T} <: Laplacian{T} A::StochasticAdjacency{T} end -@doc "Laplacian version of the AveragingAdjacency matrix." -> -type AveragingLaplacian{T} <: Laplacian{T} +""" + AveragingLaplacian{T} + +Laplacian version of the AveragingAdjacency matrix. +""" +struct AveragingLaplacian{T} <: Laplacian{T} A::AveragingAdjacency{T} end -# function passthrough{T: -function degrees(adjmat::CombinatorialAdjacency) - return adjmat.D -end -function degrees(mat::GraphMatrix) - return degrees(adjacency(mat)) -end -# function degrees(lapl::Laplacian) -# return degrees(adjacency(lapl)) -# end +""" + degrees(adjmat) -function adjacency(lapl::Laplacian) - return lapl.A -end +Return the degrees of a graph represented by the [CombinatorialAdjacency](@ref) `adjmat`. +""" +degrees(adjmat::CombinatorialAdjacency) = adjmat.D -function adjacency(lapl::GraphMatrix) - return lapl.A -end +""" + degrees(graphmx) -function convert(::Type{Adjacency}, lapl::Laplacian) - return lapl.A -end +Return the degrees of a graph represented by the graph matrix `graphmx`. +""" +degrees(mat::GraphMatrix) = degrees(adjacency(mat)) -function convert(::Type{CombinatorialAdjacency}, adjmat::Adjacency) - return adjmat.A -end +adjacency(lapl::Laplacian) = lapl.A +adjacency(lapl::GraphMatrix) = lapl.A -function convert(::Type{SparseMatrix}, adjmat::CombinatorialAdjacency) - return adjmat.A -end -function sparse{M <: Laplacian}(lapl::M) + +convert(::Type{CombinatorialAdjacency}, adjmat::Adjacency) = adjmat.A +convert(::Type{CombinatorialAdjacency}, adjmat::CombinatorialAdjacency) = adjmat + + +function sparse(lapl::M) where M<:Laplacian adjmat = adjacency(lapl) A = sparse(adjmat) L = spdiagm(diag(lapl)) - A @@ -251,50 +234,36 @@ function sparse(adjmat::Adjacency) return Diagonal(prescalefactor(adjmat)) * (A * Diagonal(postscalefactor(adjmat))) end -function convert{T}(::Type{SparseMatrix{T}}, adjmat::Adjacency{T}) +function convert(::Type{SparseMatrix{T}}, adjmat::Adjacency{T}) where T A = sparse(adjmat.A) return Diagonal(prescalefactor(adjmat)) * (A * Diagonal(postscalefactor(adjmat))) end -function convert{T}(::Type{SparseMatrix{T}}, lapl::Laplacian{T}) +function convert(::Type{SparseMatrix{T}}, lapl::Laplacian{T}) where T adjmat = adjacency(lapl) A = convert(SparseMatrix{T}, adjmat) L = spdiagm(diag(lapl)) - A return L end -function diag(lapl::CombinatorialLaplacian) - d = lapl.A.D - return d -end +diag(lapl::CombinatorialLaplacian) = lapl.A.D +diag(lapl::Laplacian) = ones(size(lapl)[2]) -function diag(lapl::Laplacian) - return ones(size(lapl)[2]) -end +*(x::AbstractArray, ::Noop) = x +*(::Noop, x) = x +*{T<:Number}(adjmat::Adjacency{T}, x::AbstractVector{T}) = + postscalefactor(adjmat) .* (adjmat.A * (prescalefactor(adjmat) .* x)) -function *(x::AbstractArray, ::Noop) - return x -end -function *(::Noop, x::Any) - return x -end -function *{T<:Number}(adjmat::Adjacency{T}, x::AbstractVector{T}) - return postscalefactor(adjmat) .* (adjmat.A * (prescalefactor(adjmat) .* x)) -end +*(adjmat::CombinatorialAdjacency{T}, x::AbstractVector{T}) where T<:Number= + adjmat.A * x -function *{T<:Number}(adjmat::CombinatorialAdjacency{T}, x::AbstractVector{T}) - return adjmat.A * x -end +*(lapl::Laplacian{T}, x::AbstractVector{T}) where T<:Number= + (diag(lapl) .* x) - (adjacency(lapl)*x) -function *{T<:Number}(lapl::Laplacian{T}, x::AbstractVector{T}) - y = adjacency(lapl)*x - z = diag(lapl) .* x - return z - y -end -function *{T<:Number}(adjmat::PunchedAdjacency{T}, x::AbstractVector{T}) +function *(adjmat::PunchedAdjacency{T}, x::AbstractVector{T}) where T<:Number y=adjmat.A*x return y - dot(adjmat.perron, y)*adjmat.perron end @@ -311,9 +280,7 @@ function A_mul_B!(Y, A::Adjacency, B) return A_mul_B!(Y, Diagonal(postscalefactor(A)), tmp) end -function A_mul_B!(Y, A::CombinatorialAdjacency, B) - return A_mul_B!(Y, A.A, B) -end +A_mul_B!(Y, A::CombinatorialAdjacency, B) = A_mul_B!(Y, A.A, B) # You can compute the StochasticAdjacency product without allocating a similar of Y. # This is true for all Adjacency where the postscalefactor is a Noop @@ -338,9 +305,12 @@ function A_mul_B!(Y, lapl::Laplacian, B) end -@doc "Symmetrize the matrix. -:triu, :tril, :sum, :or. -use :sum for weighted graphs."-> +""" + symmetrize(A::SparseMatrix, which=:or) + +Return a symmetric version of graph (represented by sparse matrix `A`) as a sparse matrix. +`which` may be one of `:triu`, `:tril`, `:sum`, or `:or`. Use `:sum` for weighted graphs. +""" function symmetrize(A::SparseMatrix, which=:or) if which==:or M = A + A' @@ -361,14 +331,30 @@ function symmetrize(A::SparseMatrix, which=:or) return M end -@doc "Only works on Adjacency because the normalizations don't commute with symmetrization"-> -function symmetrize(adjmat::CombinatorialAdjacency, which=:or) - # T = typeof(adjmat) - # @show T - # if T <: StochasticAdjacency || T <: AveragingAdjacency - # TypeError("StochasticAdjacency and AveragingAdjacency matrices are nonsymmetric. - # Use NormalizedAdjacency for this purpose") - # end - Aprime = symmetrize(adjmat.A, which) - return CombinatorialAdjacency(Aprime) -end +""" + symmetrize(adjmat, which=:or) + +Return a symmetric version of graph (represented by [`CombinatorialAdjacency`](@ref) `adjmat`) +as a [`CombinatorialAdjacency`](@ref). `which` may be one of `:triu`, `:tril`, `:sum`, or `:or`. +Use `:sum` for weighted graphs. + +### Implementation Notes +Only works on [Adjacency](@ref) because the normalizations don't commute with symmetrization. +""" +symmetrize(adjmat::CombinatorialAdjacency, which=:or) = + CombinatorialAdjacency(symmetrize(adjmat.A, which)) + + +# per #564 +@deprecate A_mul_B!(Y, A::Noop, B) None +@deprecate convert(::Type{Adjacency}, lapl::Laplacian) None +@deprecate convert(::Type{SparseMatrix}, adjmat::CombinatorialAdjacency) sparse(adjmat) + + + +""" + LinAlg + +A package for using the type system to check types of graph matrices. +""" +LinAlg diff --git a/src/linalg/nonbacktracking.jl b/src/linalg/nonbacktracking.jl index 298880c5f..131b4c513 100644 --- a/src/linalg/nonbacktracking.jl +++ b/src/linalg/nonbacktracking.jl @@ -3,13 +3,16 @@ export non_backtracking_matrix, contract!, contract -""" -Given two oriented edges i->j and k->l in g, the -non-backtraking matrix B is defined as +@doc_str """ + non_backtracking_matrix(g) + +Return a non-backtracking matrix `B` and an edgemap storing the oriented +edges' positions in `B`. -B[i->j, k->l] = δ(j,k)* (1 - δ(i,l)) +Given two arcs ``A_{i j}` and `A_{k l}` in `g`, the +non-backtraking matrix ``B`` is defined as -returns a matrix B, and an edgemap storing the oriented edges' positions in B +``B_{A_{i j}, A_{k l}} = δ_{j k} * (1 - δ_{i l})`` """ function non_backtracking_matrix(g::AbstractGraph) # idedgemap = Dict{Int, Edge}() @@ -41,27 +44,28 @@ function non_backtracking_matrix(g::AbstractGraph) return B, edgeidmap end -"""Nonbacktracking: a compact representation of the nonbacktracking operator +@doc_str """ + Nonbacktracking{G} - g: the underlying graph - edgeidmap: the association between oriented edges and index into the NBT matrix +A compact representation of the nonbacktracking operator. The Nonbacktracking operator can be used for community detection. This representation is compact in that it uses only ne(g) additional storage and provides an implicit representation of the matrix B_g defined below. -Given two oriented edges i->j and k->l in g, the -non-backtraking matrix B is defined as +Given two arcs ``A_{i j}` and `A_{k l}` in `g`, the +non-backtraking matrix ``B`` is defined as -B[i->j, k->l] = δ(j,k)* (1 - δ(i,l)) +``B_{A_{i j}, A_{k l}} = δ_{j k} * (1 - δ_{i l})`` This type is in the style of GraphMatrices.jl and supports the necessary operations for computed eigenvectors and conducting linear solves. -Additionally the contract!(vertexspace, nbt, edgespace) method takes vectors represented in -the domain of B and represents them in the domain of the adjacency matrix of g. +Additionally the `contract!(vertexspace, nbt, edgespace)` method takes vectors +represented in the domain of ``B`` and represents them in the domain of the +adjacency matrix of `g`. """ -type Nonbacktracking{G} +struct Nonbacktracking{G<:AbstractGraph} g::G edgeidmap::Dict{Edge,Int} m::Int @@ -87,7 +91,7 @@ size(nbt::Nonbacktracking) = (nbt.m,nbt.m) eltype(nbt::Nonbacktracking) = Float64 issymmetric(nbt::Nonbacktracking) = false -function *{G, T<:Number}(nbt::Nonbacktracking{G}, x::Vector{T}) +function *(nbt::Nonbacktracking, x::Vector{T}) where T<:Number length(x) == nbt.m || error("dimension mismatch") y = zeros(T, length(x)) for (e,u) in nbt.edgeidmap @@ -128,7 +132,7 @@ end sparse(nbt::Nonbacktracking) = sparse(coo_sparse(nbt)..., nbt.m,nbt.m) -function *{G, T<:Number}(nbt::Nonbacktracking{G}, x::AbstractMatrix{T}) +function *(nbt::Nonbacktracking, x::AbstractMatrix) y = zeros(x) for i in 1:nbt.m y[:,i] = nbt * x[:,i] @@ -136,20 +140,24 @@ function *{G, T<:Number}(nbt::Nonbacktracking{G}, x::AbstractMatrix{T}) return y end -"""contract!(vertexspace, nbt, edgespace) in place version of -contract(nbt, edgespace). modifies first argument +""" + contract!(vertexspace, nbt, edgespace) + +The mutating version of `contract(nbt, edgespace)`. Modifies `vertexspace`. """ function contract!(vertexspace::Vector, nbt::Nonbacktracking, edgespace::Vector) - for i=1:nv(nbt.g) - for j in neighbors(nbt.g, i) - u = nbt.edgeidmap[i > j ? Edge(j,i) : Edge(i,j)] - vertexspace[i] += edgespace[u] - end + T = eltype(nbt.g) + for i = one(T):nv(nbt.g), j in neighbors(nbt.g, i) + u = nbt.edgeidmap[i > j ? Edge(j,i) : Edge(i,j)] + vertexspace[i] += edgespace[u] end end -"""contract(nbt, edgespace) -Integrates out the edges by summing over the edges incident to each vertex. +#TODO: documentation needs work. sbromberger 20170326 +""" + contract(nbt, edgespace) + +Integrate out the edges by summing over the edges incident to each vertex. """ function contract(nbt::Nonbacktracking, edgespace::Vector) y = zeros(eltype(edgespace), nv(nbt.g)) diff --git a/src/linalg/spectral.jl b/src/linalg/spectral.jl index 5679450c4..4cb8a23fa 100644 --- a/src/linalg/spectral.jl +++ b/src/linalg/spectral.jl @@ -1,18 +1,22 @@ import Base: * export adjacency_matrix, - laplacian_matrix, - incidence_matrix, - coo_sparse, - spectral_distance +laplacian_matrix, +incidence_matrix, +coo_sparse, +spectral_distance -"""Returns a sparse boolean adjacency matrix for a graph, indexed by `[u, v]` -vertices. `true` values indicate an edge between `u` and `v`. Users may +""" + adjacency_matrix(g, dir=:out, T=Int) + +Return a sparse adjacency matrix for a graph, indexed by `[u, v]` +vertices. Non-zero values indicate an edge between `u` and `v`. Users may specify a direction (`:in`, `:out`, or `:both` are currently supported; `:out` is default for both directed and undirected graphs) and a data type for the matrix (defaults to `Int`). -Note: This function is optimized for speed. +### Implementation Notes +This function is optimized for speed and directly manipulates CSC sparse matrix fields. """ function adjacency_matrix(g::AbstractGraph, dir::Symbol=:out, T::DataType=Int) n_v = nv(g) @@ -59,50 +63,73 @@ function adjacency_matrix(g::AbstractGraph, dir::Symbol=:out, T::DataType=Int) return spmx end -adjacency_matrix(g::Graph, T::DataType=Int) = adjacency_matrix(g, :out, T) +adjacency_matrix(g::AbstractGraph, T::DataType) = adjacency_matrix(g, :out, T) + +""" + laplacian_matrix(g, dir=:unspec, T=Int) -"""Returns a sparse [Laplacian matrix](https://en.wikipedia.org/wiki/Laplacian_matrix) +Return a sparse [Laplacian matrix](https://en.wikipedia.org/wiki/Laplacian_matrix) for a graph `g`, indexed by `[u, v]` vertices. For undirected graphs, `dir` defaults to `:out`; for directed graphs, `dir` defaults to `:both`. `T` defaults to `Int` for both graph types. """ -function laplacian_matrix(g::Graph, dir::Symbol=:out, T::DataType=Int) +function laplacian_matrix(g::AbstractGraph, dir::Symbol=:unspec, T::DataType=Int) + if dir == :unspec + dir = is_directed(g)? :both : :out + end A = adjacency_matrix(g, dir, T) D = spdiagm(sum(A,2)[:]) return D - A end -function laplacian_matrix(g::DiGraph, dir::Symbol=:both, T::DataType=Int) - A = adjacency_matrix(g, dir, T) - D = spdiagm(sum(A,2)[:]) - return D - A -end +@doc_str """ + laplacian_spectrum(g, dir=:unspec, T=Int) + +Return the eigenvalues of the Laplacian matrix for a graph `g`, indexed +by vertex. Default values for `dir` and `T` are the same as those in +[`laplacian_matrix`](@ref). + +### Performance +Converts the matrix to dense with ``nv^2`` memory usage. -doc"""Returns the eigenvalues of the Laplacian matrix for a graph `g`, indexed -by vertex. Warning: Converts the matrix to dense with $nv^2$ memory usage. Use -`eigs(laplacian_matrix(g); kwargs...)` to compute some of the -eigenvalues/eigenvectors. Default values for `dir` and `T` are the same as -`laplacian_matrix`. +### Implementation Notes +Use `eigs(laplacian_matrix(g); kwargs...)` to compute some of the +eigenvalues/eigenvectors. """ -laplacian_spectrum(g::Graph, dir::Symbol=:out, T::DataType=Int) = eigvals(full(laplacian_matrix(g, dir, T))) -laplacian_spectrum(g::DiGraph, dir::Symbol=:both, T::DataType=Int) = eigvals(full(laplacian_matrix(g, dir, T))) - -doc"""Returns the eigenvalues of the adjacency matrix for a graph `g`, indexed -by vertex. Warning: Converts the matrix to dense with $nv^2$ memory usage. Use -`eigs(adjacency_matrix(g);kwargs...)` to compute some of the -eigenvalues/eigenvectors. Default values for `dir` and `T` are the same as -`adjacency_matrix`. +laplacian_spectrum(g::AbstractGraph, dir::Symbol=:unspec, T::DataType=Int) = eigvals(full(laplacian_matrix(g, dir, T))) + +@doc_str """ +Return the eigenvalues of the adjacency matrix for a graph `g`, indexed +by vertex. Default values for `dir` and `T` are the same as those in +[`adjacency_matrix`](@ref). + +### Performance +Converts the matrix to dense with ``nv^2`` memory usage. + +### Implementation Notes +Use `eigs(adjacency_matrix(g); kwargs...)` to compute some of the +eigenvalues/eigenvectors. """ -adjacency_spectrum(g::Graph, dir::Symbol=:out, T::DataType=Int) = eigvals(full(adjacency_matrix(g, dir, T))) -adjacency_spectrum(g::DiGraph, dir::Symbol=:both, T::DataType=Int) = eigvals(full(adjacency_matrix(g, dir, T))) +function adjacency_spectrum(g::AbstractGraph, dir::Symbol=:unspec, T::DataType=Int) + if dir == :unspec + dir = is_directed(g)? :both : :out + end + return eigvals(full(adjacency_matrix(g, dir, T))) +end +""" + incidence_matrix(g, T=Int) -"""Returns a sparse node-arc incidence matrix for a graph, indexed by +Return a sparse node-arc incidence matrix for a graph, indexed by `[v, i]`, where `i` is in `1:ne(g)`, indexing an edge `e`. For directed graphs, a value of `-1` indicates that `src(e) == v`, while a value of `1` indicates that `dst(e) == v`. Otherwise, the value is -`0`. For undirected graphs, if the optional keyword `oriented` is `false`, -both entries are `1`, otherwise, an arbitrary orientation is chosen. +`0`. For undirected graphs, both entries are `1` by default (this behavior +can be overridden by the `oriented` optional argument). + +### Optional Arguments +- `oriented=false`: If true, for an undirected graph `g`, the matrix will +contain arbitrary non-zero values representing connectivity between `v` and `i`. """ function incidence_matrix(g::AbstractGraph, T::DataType=Int; oriented=false) isdir = is_directed(g) @@ -131,27 +158,31 @@ function incidence_matrix(g::AbstractGraph, T::DataType=Int; oriented=false) return spmx end -"""spectral_distance(G₁, G₂ [, k]) -Compute the spectral distance between undirected n-vertex -graphs G₁ and G₂ using the top k ≤ n greatest eigenvalues. -If k is ommitted, uses full spectrum. +@doc_str """ + spectral_distance(G₁, G₂ [, k]) -For further details, please refer to: +Compute the spectral distance between undirected n-vertex +graphs `G₁` and `G₂` using the top `k` greatest eigenvalues. +If `k` is ommitted, uses full spectrum. -JOVANOVIC, I.; STANIC, Z., 2014. Spectral Distances of -Graphs Based on their Different Matrix Representations +### References +- JOVANOVIC, I.; STANIC, Z., 2014. Spectral Distances of Graphs Based on their Different Matrix Representations """ -function spectral_distance(G₁::Graph, G₂::Graph, k::Integer) - A₁ = adjacency_matrix(G₁) - A₂ = adjacency_matrix(G₂) +function spectral_distance end + +# can't use Traitor syntax here (https://github.com/mauro3/SimpleTraits.jl/issues/36) +@traitfn function spectral_distance{G<:AbstractGraph; !IsDirected{G}}(G₁::G, G₂::G, k::Integer) + A₁ = adjacency_matrix(G₁) + A₂ = adjacency_matrix(G₂) - λ₁ = k < nv(G₁)-1 ? eigs(A₁, nev=k, which=:LR)[1] : eigvals(full(A₁))[end:-1:end-(k-1)] - λ₂ = k < nv(G₂)-1 ? eigs(A₂, nev=k, which=:LR)[1] : eigvals(full(A₂))[end:-1:end-(k-1)] + λ₁ = k < nv(G₁)-1 ? eigs(A₁, nev=k, which=:LR)[1] : eigvals(full(A₁))[end:-1:end-(k-1)] + λ₂ = k < nv(G₂)-1 ? eigs(A₂, nev=k, which=:LR)[1] : eigvals(full(A₂))[end:-1:end-(k-1)] - sumabs(λ₁ - λ₂) + return sum(abs, (λ₁ - λ₂)) end -function spectral_distance(G₁::Graph, G₂::Graph) - @assert nv(G₁) == nv(G₂) "spectral distance not defined for |G₁| != |G₂|" - spectral_distance(G₁, G₂, nv(G₁)) +# can't use Traitor syntax here (https://github.com/mauro3/SimpleTraits.jl/issues/36) +@traitfn function spectral_distance{G<:AbstractGraph; !IsDirected{G}}(G₁::G, G₂::G) + @assert nv(G₁) == nv(G₂) "spectral distance not defined for |G₁| != |G₂|" + return spectral_distance(G₁, G₂, nv(G₁)) end diff --git a/src/matching/README.md b/src/matching/README.md deleted file mode 100644 index d6c2545f1..000000000 --- a/src/matching/README.md +++ /dev/null @@ -1,2 +0,0 @@ -## Please note that the matching functions have been moved to [LightGraphsExtras](https://github.com/JuliaGraphs/LightGraphsExtras.jl). - diff --git a/src/operators.jl b/src/operators.jl index 10cda7e73..16fea3bb1 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -1,8 +1,11 @@ """ complement(g) -Produces the [graph complement](https://en.wikipedia.org/wiki/Complement_graph) -of a graph. +Return the [graph complement](https://en.wikipedia.org/wiki/Complement_graph) +of a graph + +### Implementation Notes +Preserves the eltype of the input graph. """ function complement(g::Graph) gnv = nv(g) @@ -20,40 +23,41 @@ end function complement(g::DiGraph) gnv = nv(g) h = DiGraph(gnv) - for i=1:gnv - for j=1:gnv - if i != j && !has_edge(g,i,j) - add_edge!(h,i,j) - end - end + for i in vertices(g), j in vertices(g) + if i != j && !has_edge(g,i,j) + add_edge!(h,i,j) + end end return h end """ - reverse(g::DiGraph) + reverse(g) -Produces a graph where all edges are reversed from the -original. +Return a directed graph where all edges are reversed from the +original directed graph. + +### Implementation Notes +Preserves the eltype of the input graph. """ -function reverse(g::DiGraph) +function reverse end +@traitfn function reverse(g::::IsDirected) gnv = nv(g) gne = ne(g) h = DiGraph(gnv) h.fadjlist = deepcopy(g.badjlist) h.badjlist = deepcopy(g.fadjlist) h.ne = gne - h.vertices = g.vertices - return h end """ - reverse!(g::DiGraph) + reverse!(g) -In-place reverse (modifies the original graph). +In-place reverse of a directed graph (modifies the original graph). """ -function reverse!(g::DiGraph) +function reverse! end +@traitfn function reverse!(g::::IsDirected) g.fadjlist, g.badjlist = g.badjlist, g.fadjlist return g end @@ -61,12 +65,14 @@ end doc""" blkdiag(g, h) -Produces a graph with $|V(g)| + |V(h)|$ vertices and $|E(g)| + |E(h)|$ -edges. +Return a graph with ``|V(g)| + |V(h)|`` vertices and ``|E(g)| + |E(h)|`` +edges where the vertices an edges from graph `h` are appended to graph `g`. -Put simply, the vertices and edges from graph `h` are appended to graph `g`. +### Implementation Notes +Preserves the eltype of the input graph. Will error if the +number of vertices in the generated graph exceeds the eltype. """ -function blkdiag{T<:AbstractGraph}(g::T, h::T) +function blkdiag(g::T, h::T) where T<:AbstractGraph gnv = nv(g) r = T(gnv + nv(h)) for e in edges(g) @@ -81,11 +87,13 @@ end """ intersect(g, h) -Produces a graph with edges that are only in both graph `g` and graph `h`. +Return a graph with edges that are only in both graph `g` and graph `h`. -Note that this function may produce a graph with 0-degree vertices. +### Implementation Notes +This function may produce a graph with 0-degree vertices. +Preserves the eltype of the input graph. """ -function intersect{T<:AbstractGraph}(g::T, h::T) +function intersect(g::T, h::T) where T<:AbstractGraph gnv = nv(g) hnv = nv(h) @@ -99,11 +107,13 @@ end """ difference(g, h) -Produces a graph with edges in graph `g` that are not in graph `h`. +Return a graph with edges in graph `g` that are not in graph `h`. +### Implementation Notes Note that this function may produce a graph with 0-degree vertices. +Preserves the eltype of the input graph. """ -function difference{T<:AbstractGraph}(g::T, h::T) +function difference(g::T, h::T) where T<:AbstractGraph gnv = nv(g) hnv = nv(h) @@ -117,12 +127,15 @@ end """ symmetric_difference(g, h) -Produces a graph with edges from graph `g` that do not exist in graph `h`, +Return a graph with edges from graph `g` that do not exist in graph `h`, and vice versa. +### Implementation Notes Note that this function may produce a graph with 0-degree vertices. +Preserves the eltype of the input graph. Will error if the +number of vertices in the generated graph exceeds the eltype. """ -function symmetric_difference{T<:AbstractGraph}(g::T, h::T) +function symmetric_difference(g::T, h::T) where T<:AbstractGraph gnv = nv(g) hnv = nv(h) @@ -139,15 +152,20 @@ end """ union(g, h) -Merges graphs `g` and `h` by taking the set union of all vertices and edges. +Return a graph that combines graphs `g` and `h` by taking the set union +of all vertices and edges. + +### Implementation Notes +Preserves the eltype of the input graph. Will error if the +number of vertices in the generated graph exceeds the eltype. """ -function union{T<:AbstractGraph}(g::T, h::T) +function union(g::T, h::T) where T<:AbstractGraph gnv = nv(g) hnv = nv(h) r = T(max(gnv, hnv)) r.ne = ne(g) - for i = 1:gnv + for i in vertices(g) r.fadjlist[i] = deepcopy(g.fadjlist[i]) if is_directed(g) r.badjlist[i] = deepcopy(g.badjlist[i]) @@ -163,12 +181,16 @@ end """ join(g, h) -Merges graphs `g` and `h` using `blkdiag` and then adds all the edges between - the vertices in `g` and those in `h`. +Return a graph that combines graphs `g` and `h` using `blkdiag` and then +adds all the edges between the vertices in `g` and those in `h`. + +### Implementation Notes +Preserves the eltype of the input graph. Will error if the number of vertices +in the generated graph exceeds the eltype. """ -function join(g::Graph, h::Graph) +function join(g::T, h::T) where T<:AbstractGraph r = blkdiag(g, h) - for i=1:nv(g) + for i in vertices(g) for j=nv(g)+1:nv(g)+nv(h) add_edge!(r, i, j) end @@ -180,16 +202,26 @@ end """ crosspath(len::Integer, g::Graph) -Replicate `len` times `h` and connect each vertex with its copies in a path -""" -crosspath(len::Integer, g::Graph) = cartesian_product(PathGraph(len), g) +Return a graph that duplicates `g` `len` times and connects each vertex +with its copies in a path. +### Implementation Notes +Preserves the eltype of the input graph. Will error if the number of vertices +in the generated graph exceeds the eltype. +""" +function crosspath end +@traitfn function crosspath(len::Integer, g::::(!IsDirected)) + T = eltype(g) + p = PathGraph(len) + h = Graph{T}(p) + return cartesian_product(h, g) +end # The following operators allow one to use a LightGraphs.Graph as a matrix in eigensolvers for spectral ranking and partitioning. # """Provides multiplication of a graph `g` by a vector `v` such that spectral # graph functions in [GraphMatrices.jl](https://github.com/jpfairbanks/GraphMatrices.jl) can utilize LightGraphs natively. # """ -function *{T<:Real}(g::Graph, v::Vector{T}) +function *(g::Graph, v::Vector{T}) where T<:Real length(v) == nv(g) || error("Vector size must equal number of vertices") y = zeros(T, nv(g)) for e in edges(g) @@ -201,7 +233,7 @@ function *{T<:Real}(g::Graph, v::Vector{T}) return y end -function *{T<:Real}(g::DiGraph, v::Vector{T}) +function *(g::DiGraph, v::Vector{T}) where T<:Real length(v) == nv(g) || error("Vector size must equal number of vertices") y = zeros(T, nv(g)) for e in edges(g) @@ -212,7 +244,11 @@ function *{T<:Real}(g::DiGraph, v::Vector{T}) return y end -"""sum(g,i) provides 1:indegree or 2:outdegree vectors""" +""" + sum(g, i) + +Return a vector of indegree (`i`=1) or outdegree (`i`=2) values for graph `g`. +""" function sum(g::AbstractGraph, dim::Int) dim == 1 && return indegree(g, vertices(g)) dim == 2 && return outdegree(g, vertices(g)) @@ -221,17 +257,29 @@ end size(g::AbstractGraph) = (nv(g), nv(g)) -"""size(g,i) provides 1:nv or 2:nv else 1 """ +""" + size(g, i) + +Return the number of vertices in `g` if `i`=1 or `i`=2, or `1` otherwise. +""" size(g::Graph,dim::Int) = (dim == 1 || dim == 2)? nv(g) : 1 -"""sum(g) provides the number of edges in the graph""" +""" + sum(g) + +Return the number of edges in `g` +""" sum(g::AbstractGraph) = ne(g) -"""sparse(g) is the adjacency_matrix of g""" +""" + sparse(g) + +Return the default adjacency matrix of `g`. +""" sparse(g::AbstractGraph) = adjacency_matrix(g) #arrayfunctions = (:eltype, :length, :ndims, :size, :strides, :issymmetric) -eltype(g::AbstractGraph) = Float64 +# eltype(g::AbstractGraph) = Float64 length(g::AbstractGraph) = nv(g)*nv(g) ndims(g::AbstractGraph) = 2 issymmetric(g::AbstractGraph) = !is_directed(g) @@ -239,9 +287,14 @@ issymmetric(g::AbstractGraph) = !is_directed(g) """ cartesian_product(g, h) -Returns the (cartesian product)[https://en.wikipedia.org/wiki/Tensor_product_of_graphs] of `g` and `h` +Return the (cartesian product)[https://en.wikipedia.org/wiki/Tensor_product_of_graphs] +of `g` and `h`. + +### Implementation Notes +Preserves the eltype of the input graph. Will error if the number of vertices +in the generated graph exceeds the eltype. """ -function cartesian_product{G<:AbstractGraph}(g::G, h::G) +function cartesian_product(g::G, h::G) where G<:AbstractGraph z = G(nv(g)*nv(h)) id(i, j) = (i-1)*nv(h) + j for e in edges(g) @@ -253,7 +306,7 @@ function cartesian_product{G<:AbstractGraph}(g::G, h::G) for e in edges(h) j1, j2 = Tuple(e) - for i=1:nv(g) + for i in vertices(g) add_edge!(z, id(i,j1), id(i,j2)) end end @@ -263,9 +316,14 @@ end """ tensor_product(g, h) -Returns the (tensor product)[https://en.wikipedia.org/wiki/Tensor_product_of_graphs] of `g` and `h` +Return the (tensor product)[https://en.wikipedia.org/wiki/Tensor_product_of_graphs] +of `g` and `h`. + +### Implementation Notes +Preserves the eltype of the input graph. Will error if the number of vertices +in the generated graph exceeds the eltype. """ -function tensor_product{G<:AbstractGraph}(g::G, h::G) +function tensor_product(g::G, h::G) where G<:AbstractGraph z = G(nv(g)*nv(h)) id(i, j) = (i-1)*nv(h) + j for e1 in edges(g) @@ -283,47 +341,48 @@ end """ induced_subgraph(g, vlist) + induced_subgraph(g, elist) -Returns the subgraph of `g` induced by the vertices in `vlist`. +Return the subgraph of `g` induced by the vertices in `vlist` or edges in `elist` +along with a vector mapping the new vertices to the old ones +(the vertex `i` in the subgraph corresponds to the vertex `vmap[i]` in `g`.) The returned graph has `length(vlist)` vertices, with the new vertex `i` corresponding to the vertex of the original graph in the `i`-th position of `vlist`. -Returns also a vector `vmap` mapping the new vertices to the -old ones: the vertex `i` in the subgraph corresponds to -the vertex `vmap[i]` in `g`. +### Usage Examples +```doctestjl +julia> g = CompleteGraph(10) - induced_subgraph(g, elist) +julia> sg, vmap = subgraph(g, 5:8) + +julia> @assert g[5:8] == sg -Returns the subgraph of `g` induced by the edges in `elist`, along with -the associated vector `vmap` mapping new vertices to the old ones. +julia> @assert nv(sg) == 4 +julia> @assert ne(sg) == 6 -### Usage Examples: -```julia -g = CompleteGraph(10) -sg, vmap = subgraph(g, 5:8) -@assert g[5:8] == sg -@assert nv(sg) == 4 -@assert ne(sg) == 6 -@assert vm[4] == 8 +julia> @assert vm[4] == 8 -sg, vmap = subgraph(g, [2,8,3,4]) -@asssert sg == g[[2,8,3,4]] +julia> sg, vmap = subgraph(g, [2,8,3,4]) -elist = [Edge(1,2), Edge(3,4), Edge(4,8)] -sg, vmap = subgraph(g, elist) -@asssert sg == g[elist] +julia> @assert sg == g[[2,8,3,4]] + +julia> elist = [Edge(1,2), Edge(3,4), Edge(4,8)] + +julia> sg, vmap = subgraph(g, elist) + +julia> @assert sg == g[elist] ``` """ -function induced_subgraph{T<:AbstractGraph}(g::T, vlist::AbstractVector{Int}) +function induced_subgraph(g::T, vlist::AbstractVector{U}) where T<:AbstractGraph where U<:Integer allunique(vlist) || error("Vertices in subgraph list must be unique") h = T(length(vlist)) - newvid = Dict{Int, Int}() - vmap =Vector{Int}(length(vlist)) + newvid = Dict{U, U}() + vmap =Vector{U}(length(vlist)) for (i,v) in enumerate(vlist) - newvid[v] = i + newvid[v] = U(i) vmap[i] = v end @@ -341,10 +400,11 @@ function induced_subgraph{T<:AbstractGraph}(g::T, vlist::AbstractVector{Int}) end -function induced_subgraph{T<:AbstractGraph}(g::T, elist::AbstractVector{Edge}) - h = T() - newvid = Dict{Int, Int}() - vmap = Vector{Int}() +function induced_subgraph(g::T, elist::AbstractVector{U}) where T<:AbstractGraph where U<:AbstractEdge + h = zero(g) + et = eltype(h) + newvid = Dict{et, et}() + vmap = Vector{et}() for e in elist u, v = Tuple(e) @@ -364,17 +424,21 @@ end """ g[iter] -Returns the subgraph induced by `iter`. Equivalent to [`induced_subgraph`](@ref)`(g, iter)[1]`. +Return the subgraph induced by `iter`. +Equivalent to [`induced_subgraph`](@ref)`(g, iter)[1]`. """ getindex(g::AbstractGraph, iter) = induced_subgraph(g, iter)[1] """ - egonet(g, v::Int, d::Int; dir=:out) + egonet(g, v:, d) + +Return the subgraph of `g` induced by the neighbors of `v` up to distance +`d`. +This is equivalent to [`induced_subgraph`](@ref)`(g, neighborhood(g, v, d, dir=dir))[1].` -Returns the subgraph of `g` induced by the neighbors of `v` up to distance -`d`. If `g` is a `DiGraph` the `dir` optional argument specifies -the edge direction the edge direction with respect to `v` (i.e. `:in` or `:out`) -to be considered. This is equivalent to [`induced_subgraph`](@ref)`(g, neighborhood(g, v, d, dir=dir))[1].` +### Optional Arguments +- `dir=:out`: if `g` is directed, this argument specifies the edge direction +with respect to `v` (i.e. `:in` or `:out`). """ -egonet(g::AbstractGraph, v::Int, d::Int; dir=:out) = g[neighborhood(g, v, d, dir=dir)] +egonet(g::AbstractGraph, v::Integer, d::Integer; dir=:out) = g[neighborhood(g, v, d, dir=dir)] diff --git a/src/persistence/common.jl b/src/persistence/common.jl index 407c53be6..936624e57 100644 --- a/src/persistence/common.jl +++ b/src/persistence/common.jl @@ -1,7 +1,5 @@ NI(x...) = error("This function is not implemented.") -@deprecate readgraph load - # filemap is filled in the format specific source files const filemap = Dict{Symbol, Tuple{Function, Function, Function, Function}}() # :gml => (loadgml, loadgml_mult, savegml, savegml_mult) @@ -13,9 +11,8 @@ const filemap = Dict{Symbol, Tuple{Function, Function, Function, Function}}() """ loadgraph(file, t=:lg) -Reads a graph from `file` in the format `t`. - -Supported formats are `:lg, :gml, :dot, :graphml, :gexf, :net, :jld, :graph6`. +Read a graph from `file` in the format `t`. +Supported formats are `:lg`, `:gml`, `:dot`, `:graphml`, `:gexf`, `:net`, `:jld`, and `:graph6`. """ function loadgraph(fn::String, x...) GZip.open(fn,"r") do io @@ -33,9 +30,9 @@ end """ load(file, name, t=:lg) -Loads a graph with name `name` from `file` in format `t`. +Load a graph with name `name` from `file` in format `t`. -Currently supported formats are `:lg, :gml, :graphml, :gexf, :dot, :net`. +Currently supported formats are `:lg`, `:gml`, `:graphml`, `:gexf`, `:dot`, `:net`, and `graph6`. """ function load(io::IO, gname::String, t::Symbol=:lg) t in keys(filemap) || error("Please select a supported graph format: one of $(keys(filemap))") @@ -45,7 +42,7 @@ end """ load(file, t=:lg) -Loads multiple graphs from `file` in the format `t`. Returns a dictionary +Load multiple graphs from `file` in the format `t`. Return a dictionary mapping graph name to graph. For unnamed graphs the default names \"graph\" and \"digraph\" will be used. @@ -65,9 +62,11 @@ end """ + savegraph(f, g, ...) + Save a graph to file. See [`save`](@ref). """ -savegraph{T<:AbstractGraph}(f, g::T, x...; kws...) = save(f, g::T, x...; kws...) +savegraph(f, g::AbstractGraph, x...; kws...) = save(f, g, x...; kws...) """ save(file, g, t=:lg) @@ -75,25 +74,23 @@ savegraph{T<:AbstractGraph}(f, g::T, x...; kws...) = save(f, g::T, x...; kws...) save(file, dict, t=:lg) Saves a graph `g` with name `name` to `file` in the format `t`. If `name` is not given -the default names \"graph\" and \"digraph\" will be used. +the default names \"graph\" and \"digraph\" will be used. Return the number of graphs written. -Currently supported formats are `:lg, :gml, :graphml, :gexf, :dot, :net, :graph6`. +Currently supported formats are `:lg`, `:gml`, :`graphml`, `:gexf`, `:dot`, `:net`, and `:graph6`. For some graph formats, multiple graphs in a `dict` `"name"=>g` can be saved in the same file. - -Returns the number of graphs written. """ -function save{T<:AbstractGraph}(io::IO, g::T, gname::String, t::Symbol=:lg) +function save(io::IO, g::AbstractGraph, gname::String, t::Symbol=:lg) t in keys(filemap) || error("Please select a supported graph format: one of $(keys(filemap))") return filemap[t][3](io, g, gname) end # save a single graph without name -save(io::IO, g::Graph, t::Symbol=:lg) = save(io, g, "graph", t) -save(io::IO, g::DiGraph, t::Symbol=:lg) = save(io, g, "digraph", t) +save(io::IO, g::AbstractGraph, t::Symbol=:lg) = + save(io, g, is_directed(g)? "digraph" : "graph", t) # save a dictionary of graphs {"name" => graph} -function save{T<:String, U<:AbstractGraph}(io::IO, d::Dict{T, U}, t::Symbol=:lg) +function save(io::IO, d::Dict{T, U}, t::Symbol=:lg) where T<:AbstractString where U<:AbstractGraph t in keys(filemap) || error("Please select a supported graph format: one of $(keys(filemap))") return filemap[t][4](io, d) end diff --git a/src/persistence/dot.jl b/src/persistence/dot.jl index 993a9c4cd..184dba829 100644 --- a/src/persistence/dot.jl +++ b/src/persistence/dot.jl @@ -20,7 +20,7 @@ function _dot_read_one_graph(pg::DOT.Graph) end function loaddot(io::IO, gname::String) - p = DOT.parse_dot(readall(io)) + p = DOT.parse_dot(readstring(io)) for pg in p isdir = pg.directed possname = isdir? DOT.StringID("digraph") : DOT.StringID("graph") @@ -31,7 +31,7 @@ function loaddot(io::IO, gname::String) end function loaddot_mult(io::IO) - p = DOT.parse_dot(readall(io)) + p = DOT.parse_dot(readstring(io)) graphs = Dict{String, AbstractGraph}() diff --git a/src/persistence/gexf.jl b/src/persistence/gexf.jl index 4f62f0c1c..ff85122e9 100644 --- a/src/persistence/gexf.jl +++ b/src/persistence/gexf.jl @@ -2,15 +2,12 @@ # TODO: implement readgexf """ -savegexf(f::IO, g::AbstractGraph, gname::String) + savegexf(f, g, gname) -Writes a graph `g` with name `gname` -to a file `f` in the -[Gexf](http://gexf.net/format/) format. - -Returns 1 (number of graphs written). +Write a graph `g` with name `gname` to an IO stream `io` in the +[Gexf](http://gexf.net/format/) format. Return 1 (number of graphs written). """ -function savegexf(f::IO, g::AbstractGraph, gname::String) +function savegexf(io::IO, g::AbstractGraph, gname::String) xdoc = XMLDocument() xroot = setroot!(xdoc, ElementNode("gexf")) xroot["xmlns"] = "http://www.gexf.net/1.2draft" @@ -40,7 +37,7 @@ function savegexf(f::IO, g::AbstractGraph, gname::String) m += 1 end - prettyprint(f, xdoc) + prettyprint(io, xdoc) return 1 end diff --git a/src/persistence/gml.jl b/src/persistence/gml.jl index 9ff0a5b2d..34cd2eea3 100644 --- a/src/persistence/gml.jl +++ b/src/persistence/gml.jl @@ -16,7 +16,7 @@ function _gml_read_one_graph(gs, dir) return g end function loadgml(io::IO, gname::String) - p = GML.parse_dict(readall(io)) + p = GML.parse_dict(readstring(io)) for gs in p[:graph] dir = Bool(get(gs, :directed, 0)) graphname = get(gs, :label, dir ? "digraph" : "graph") @@ -27,7 +27,7 @@ function loadgml(io::IO, gname::String) end function loadgml_mult(io::IO) - p = GML.parse_dict(readall(io)) + p = GML.parse_dict(readstring(io)) graphs = Dict{String, AbstractGraph}() for gs in p[:graph] dir = Bool(get(gs, :directed, 0)) @@ -38,11 +38,10 @@ function loadgml_mult(io::IO) end """ -savegml(f, g, gname = "graph") + savegml(f, g, gname="graph") -Writes a graph `g` with name `gname` -to a file `f` in the -[GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language) format. +Write a graph `g` with name `gname` to an IO stream `io` in the +[GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language) format. Return 1. """ function savegml(io::IO, g::AbstractGraph, gname::String = "") println(io, "graph") @@ -68,9 +67,9 @@ function savegml(io::IO, g::AbstractGraph, gname::String = "") end -"""Writes a dictionary of (name=>graph) to a file `fn` - -Returns number of graphs written. +""" + savegml_mult(io, graphs) +Write a dictionary of (name=>graph) to an IO stream `io` Return number of graphs written. """ function savegml_mult(io::IO, graphs::Dict) ng = 0 diff --git a/src/persistence/graph6.jl b/src/persistence/graph6.jl index 34988efdc..1b29ef790 100644 --- a/src/persistence/graph6.jl +++ b/src/persistence/graph6.jl @@ -69,7 +69,11 @@ function _g6_Np(N::Vector{UInt8}) end -"""Given a graph, create the corresponding Graph6 string""" +""" + _graphToG6String(g) + +Given a graph `g`, create the corresponding Graph6 string. +""" function _graphToG6String(g::Graph) A = adjacency_matrix(g, Bool) n = nv(g) @@ -84,7 +88,7 @@ function _graphToG6String(g::Graph) return join([">>graph6<<", String(_g6_N(n)), String(_g6_R(x))]) end -function _g6StringToGraph(s::String) +function _g6StringToGraph(s::AbstractString) if startswith(s, ">>graph6<<") s = s[11:end] end @@ -120,15 +124,20 @@ function loadgraph6_mult(io::IO) return graphdict end -"""Reads a graph from file `fname` in the [Graph6](http://users.cecs.anu.edu.au/%7Ebdm/data/formats.txt) format. - Returns the graph. +""" + loadgraph6(io, gname="g1") + +Read a graph from IO stream `io` in the [Graph6](http://users.cecs.anu.edu.au/%7Ebdm/data/formats.txt) +format. Return the graph. """ loadgraph6(io::IO, gname::String="g1") = loadgraph6_mult(io)[gname] """ -Writes a graph `g` to a file `f` in the [Graph6](http://users.cecs.anu.edu.au/%7Ebdm/data/formats.txt) format. -Returns 1 (number of graphs written). + savegraph6(io, g, gname="g") + +Write a graph `g` to IO stream `io` in the [Graph6](http://users.cecs.anu.edu.au/%7Ebdm/data/formats.txt) +format. Return 1 (number of graphs written). """ function savegraph6(io::IO, g::AbstractGraph, gname::String = "g") str = _graphToG6String(g) diff --git a/src/persistence/graphml.jl b/src/persistence/graphml.jl index 28222046e..2fde28fb7 100644 --- a/src/persistence/graphml.jl +++ b/src/persistence/graphml.jl @@ -26,7 +26,7 @@ function _graphml_read_one_graph(el::EzXML.Node, isdirected::Bool) end function loadgraphml(io::IO, gname::String) - xdoc = parsexml(readall(io)) + xdoc = parsexml(readstring(io)) xroot = root(xdoc) # an instance of XMLElement name(xroot) == "graphml" || error("Not a GraphML file") @@ -51,7 +51,7 @@ function loadgraphml(io::IO, gname::String) end function loadgraphml_mult(io::IO) - xdoc = parsexml(readall(io)) + xdoc = parsexml(readstring(io)) xroot = root(xdoc) # an instance of XMLElement name(xroot) == "graphml" || error("Not a GraphML file") diff --git a/src/persistence/jld.jl b/src/persistence/jld.jl index 6f442a407..08aded632 100644 --- a/src/persistence/jld.jl +++ b/src/persistence/jld.jl @@ -1,5 +1,8 @@ using JLD -"""GraphSerializer is a type for custom serialization into JLD files. +""" + GraphSerializer + +GraphSerializer is a type for custom serialization into JLD files. It has no use except on disk. This type supports JLD.writeas(g::Graph) and JLD.readas(gs::GraphSerializer). It is a form of Compressed Sparse Column format of the adjacency matrix of a graph g. @@ -19,9 +22,9 @@ This format is fine for storage on disk because the data is not changing. JLD.readas(gs::GraphSerializer) creates the adjacency list representation directly instead of calling add_edge! repeatedly in an attempt to improve performance. -This type has not been tested with mmaped files or compression in JLD. +This type has not been tested with mmaped files or compression in JLD. """ -type GraphSerializer +mutable struct GraphSerializer vertices::UnitRange{Int} ne::Int packed_adjlist::Vector{Int} @@ -31,7 +34,7 @@ end function JLD.writeas(g::Graph) n_adjlist = zeros(Int,nv(g)) @assert sum(degree(g))/2 == ne(g) - packed_adjlist = Array(Int, 2*ne(g)) + packed_adjlist = Vector{Int}(2*ne(g)) k = 0 degree(g), sum(degree(g)) for (i,lst) in enumerate(g.fadjlist) @@ -47,7 +50,7 @@ function JLD.writeas(g::Graph) packed_adjlist[k+=1] = v end end - GraphSerializer(g.vertices, g.ne, packed_adjlist, n_adjlist) + GraphSerializer(vertices(g), ne(g), packed_adjlist, n_adjlist) end function JLD.readas(gs::GraphSerializer) @@ -64,11 +67,11 @@ function JLD.readas(gs::GraphSerializer) adj[i] = gs.packed_adjlist[posbegin:posend] end @assert sum(map(length, adj)) == 2gs.ne - g = Graph(1:n, gs.ne, adj) + g = Graph(gs.ne, adj) return g end -type Network{G,V,E} +mutable struct Network{G,V,E} graph::G vprop::Vector{V} eprop::Dict{Edge,E} diff --git a/src/persistence/lg.jl b/src/persistence/lg.jl index 163cf2bb2..cef7fe7c9 100644 --- a/src/persistence/lg.jl +++ b/src/persistence/lg.jl @@ -35,7 +35,11 @@ function _lg_skip_one_graph(f::IO, n_e::Integer) end end -"""Returns a dictionary of (name=>graph) loaded from file `fn`.""" +""" + loadlg_mult(io) + +Return a dictionary of (name=>graph) loaded from IO stream `io`. +""" function loadlg_mult(io::IO) graphs = Dict{String, AbstractGraph}() while !eof(io) @@ -76,10 +80,11 @@ function loadlg(io::IO, gname::String) error("Graph $gname not found") end -"""Writes a graph `g` with name `graphname` in a proprietary format -to the IO stream designated by `io`. +""" + savelg(io, g, gname) -Returns 1 (number of graphs written). +Write a graph `g` with name `gname` in a proprietary format +to the IO stream designated by `io`. Return 1 (number of graphs written). """ function savelg(io::IO, g::AbstractGraph, gname::String) # write header line @@ -93,10 +98,11 @@ function savelg(io::IO, g::AbstractGraph, gname::String) return 1 end -"""Writes a dictionary of (name=>graph) to a file `fn`, -with default `GZip` compression. +""" + savelg_mult(io, graphs) -Returns number of graphs written. +Write a dictionary of (name=>graph) to an IO stream `io`, +with default `GZip` compression. Return number of graphs written. """ function savelg_mult(io::IO, graphs::Dict) ng = 0 diff --git a/src/persistence/net.jl b/src/persistence/net.jl index a2efdaf8f..15dc8fd7f 100644 --- a/src/persistence/net.jl +++ b/src/persistence/net.jl @@ -1,37 +1,40 @@ """ -Writes a graph `g` to a file `f` in the [Pajek -NET](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) format. -Returns 1 (number of graphs written). + savenet(io, g, gname="g") + +Write a graph `g` to an IO stream `io` in the [Pajek NET](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) +format. Return 1 (number of graphs written). """ -function savenet(f::IO, g::AbstractGraph, gname::String = "g") - println(f, "*Vertices $(nv(g))") +function savenet(io::IO, g::AbstractGraph, gname::String = "g") + println(io, "*Vertices $(nv(g))") # write edges if is_directed(g) - println(f, "*Arcs") + println(io, "*Arcs") else - println(f, "*Edges") + println(io, "*Edges") end for e in edges(g) - println(f, "$(src(e)) $(dst(e))") + println(io, "$(src(e)) $(dst(e))") end return 1 end -"""Reads a graph from file `fname` in the [Pajek - NET](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) format. - Returns the graph. """ -function loadnet(f::IO, gname::String = "g") - line =readline(f) + loadnet(io::IO, gname="g") + +Read a graph from IO stream `io` in the [Pajek NET](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) +format. Return the graph. +""" +function loadnet(io::IO, gname::String = "g") + line =readline(io) # skip comments while startswith(line, "%") - line =readline(f) + line =readline(io) end n = parse(Int, matchall(r"\d+",line)[1]) - for fline in eachline(f) - line = fline + for ioline in eachline(io) + line = ioline (ismatch(r"^\*Arcs",line) || ismatch(r"^\*Edges",line)) && break end if ismatch(r"^\*Arcs",line) @@ -40,16 +43,16 @@ function loadnet(f::IO, gname::String = "g") g = Graph(n) end while ismatch(r"^\*Arcs",line) - for fline in eachline(f) - line = fline + for ioline in eachline(io) + line = ioline m = matchall(r"\d+",line) length(m) < 2 && break add_edge!(g, parse(Int, m[1]), parse(Int, m[2])) end end while ismatch(r"^\*Edges",line) # add edges in both directions - for fline in eachline(f) - line = fline + for ioline in eachline(io) + line = ioline m = matchall(r"\d+",line) length(m) < 2 && break i1,i2 = parse(Int, m[1]), parse(Int, m[2]) diff --git a/src/shortestpaths/astar.jl b/src/shortestpaths/astar.jl index 1fc29b56e..6725e0c26 100644 --- a/src/shortestpaths/astar.jl +++ b/src/shortestpaths/astar.jl @@ -3,12 +3,12 @@ # A* shortest-path algorithm -function a_star_impl!{T<:Number}( - graph::AbstractGraph,# the graph - t::Int, # the end vertex +function a_star_impl!( + g::AbstractGraph,# the graph + t::Integer, # the end vertex frontier, # an initialized heap containing the active vertices colormap::Vector{Int}, # an (initialized) color-map to indicate status of vertices - distmx::AbstractArray{T, 2}, + distmx::AbstractMatrix, heuristic::Function # heuristic fn (under)estimating distance to target ) @@ -18,40 +18,45 @@ function a_star_impl!{T<:Number}( return path end - for v in LightGraphs.fadj(graph, u) + for v in LightGraphs.out_neighbors(g, u) if colormap[v] < 2 dist = distmx[u, v] - colormap[v] = 1 new_path = cat(1, path, Edge(u,v)) path_cost = cost_so_far + dist enqueue!(frontier, - (path_cost, new_path, v), - path_cost + heuristic(v)) + (path_cost, new_path, v), + path_cost + heuristic(v)) end end colormap[u] = 2 end - nothing + Vector{Edge}() end -"""Computes the shortest path between vertices `s` and `t` using the -[A\* search algorithm](http://en.wikipedia.org/wiki/A%2A_search_algorithm). An -optional heuristic function and edge distance matrix may be supplied. """ -function a_star{T<:Number}( - graph::AbstractGraph, # the graph + a_star(g, s, t[, distmx][, heuristic]) + +Return a vector of edges comprising the shortest path between vertices `s` and `t` +using the [A\* search algorithm](http://en.wikipedia.org/wiki/A%2A_search_algorithm). +An optional heuristic function and edge distance matrix may be supplied. If missing, +the distance matrix is set to [`DefaultDistance`](@ref) and the heuristic is set to +`n -> 0`. +""" +function a_star( + g::AbstractGraph, # the g - s::Int, # the start vertex - t::Int, # the end vertex - distmx::AbstractArray{T, 2} = LightGraphs.DefaultDistance(), + s::Integer, # the start vertex + t::Integer, # the end vertex + distmx::AbstractMatrix{T} = LightGraphs.DefaultDistance(), heuristic::Function = n -> 0 - ) - # heuristic (under)estimating distance to target - frontier = PriorityQueue(Tuple{T,Array{Edge,1},Int},T) + ) where T + # heuristic (under)estimating distance to target + U = eltype(g) + frontier = PriorityQueue(Tuple{T,Vector{Edge},U},T) frontier[(zero(T), Vector{Edge}(), s)] = zero(T) - colormap = zeros(Int, nv(graph)) + colormap = zeros(Int, nv(g)) colormap[s] = 1 - a_star_impl!(graph, t, frontier, colormap, distmx, heuristic) + a_star_impl!(g, t, frontier, colormap, distmx, heuristic) end diff --git a/src/shortestpaths/bellman-ford.jl b/src/shortestpaths/bellman-ford.jl index 2a9ede753..9e468851a 100644 --- a/src/shortestpaths/bellman-ford.jl +++ b/src/shortestpaths/bellman-ford.jl @@ -9,33 +9,38 @@ # ################################################################### -type NegativeCycleError <: Exception end +struct NegativeCycleError <: Exception end # AbstractPathState is defined in core -type BellmanFordState{T<:Number}<:AbstractPathState - parents::Vector{Int} +""" + BellmanFordState{T, U} + +An `AbstractPathState` designed for Bellman-Ford shortest-paths calculations. +""" +struct BellmanFordState{T<:Number, U<:Integer}<:AbstractPathState + parents::Vector{U} dists::Vector{T} end -function bellman_ford_shortest_paths!{R<:Real}( +function bellman_ford_shortest_paths!( graph::AbstractGraph, - sources::AbstractVector{Int}, - distmx::AbstractArray{R, 2}, + sources::AbstractVector{T}, + distmx::AbstractMatrix{R}, state::BellmanFordState -) + ) where R<:Real where T<:Integer - active = Set{Int}() + active = Set{T}() for v in sources state.dists[v] = 0 state.parents[v] = 0 push!(active, v) end no_changes = false - for i in 1:nv(graph) + for i in one(T):nv(graph) no_changes = true - new_active = Set{Int}() + new_active = Set{T}() for u in active - for v in fadj(graph, u) + for v in out_neighbors(graph, u) edist = distmx[u, v] if state.dists[v] > state.dists[u] + edist state.dists[v] = state.dists[u] + edist @@ -54,45 +59,49 @@ function bellman_ford_shortest_paths!{R<:Real}( return state end -"""Uses the [Bellman-Ford algorithm](http://en.wikipedia.org/wiki/Bellman–Ford_algorithm) -to compute shortest paths between a source vertex `s` or a set of source -vertices `ss`. Returns a `BellmanFordState` with relevant traversal information -(see below). """ -function bellman_ford_shortest_paths{T}( - graph::AbstractGraph, + bellman_ford_shortest_paths(g, s, distmx=DefaultDistance()) + bellman_ford_shortest_paths(g, ss, distmx=DefaultDistance()) - sources::AbstractVector{Int}, - distmx::AbstractArray{T, 2} = DefaultDistance() - ) +Compute shortest paths between a source `s` (or list of sources `ss`) and all +other nodes in graph `g` using the [Bellman-Ford algorithm](http://en.wikipedia.org/wiki/Bellman–Ford_algorithm). +Return a [`BellmanFordState`](@ref) with relevant traversal information. +""" +function bellman_ford_shortest_paths( + graph::AbstractGraph, + sources::AbstractVector{U}, + distmx::AbstractMatrix{T} = DefaultDistance() + ) where T where U<:Integer nvg = nv(graph) - state = BellmanFordState(zeros(Int,nvg), fill(typemax(T), nvg)) + state = BellmanFordState(zeros(U,nvg), fill(typemax(T), nvg)) bellman_ford_shortest_paths!(graph, sources, distmx, state) end -bellman_ford_shortest_paths{T}( +bellman_ford_shortest_paths( graph::AbstractGraph, - v::Int, - distmx::AbstractArray{T, 2} = DefaultDistance() -) = bellman_ford_shortest_paths(graph, [v], distmx) + v::Integer, + distmx::AbstractMatrix = DefaultDistance() + ) = bellman_ford_shortest_paths(graph, [v], distmx) -function has_negative_edge_cycle(graph::AbstractGraph) +has_negative_edge_cycle(g::AbstractGraph) = false + +function has_negative_edge_cycle(g::AbstractGraph, distmx::AbstractMatrix) try - bellman_ford_shortest_paths(graph, vertices(graph)) + bellman_ford_shortest_paths(g, vertices(g), distmx) catch e isa(e, NegativeCycleError) && return true end return false end -function enumerate_paths(state::AbstractPathState, dest::Vector{Int}) +function enumerate_paths(state::AbstractPathState, vs::Vector{T}) where T<:Integer parents = state.parents - num_dest = length(dest) - all_paths = Array(Vector{Int},num_dest) - for i=1:num_dest - all_paths[i] = Vector{Int}() - index = dest[i] + num_vs = length(vs) + all_paths = Vector{Vector{T}}(num_vs) + for i=1:num_vs + all_paths[i] = Vector{T}() + index = vs[i] if parents[index] != 0 || parents[index] == index while parents[index] != 0 push!(all_paths[i], index) @@ -105,18 +114,21 @@ function enumerate_paths(state::AbstractPathState, dest::Vector{Int}) all_paths end -enumerate_paths(state::AbstractPathState, dest) = enumerate_paths(state, [dest])[1] +enumerate_paths(state::AbstractPathState, v) = enumerate_paths(state, [v])[1] enumerate_paths(state::AbstractPathState) = enumerate_paths(state, [1:length(state.parents);]) -"""Given a path state `state` of type `AbstractPathState` (see below), returns a +""" + enumerate_paths(state[, vs]) +Given a path state `state` of type `AbstractPathState`, return a vector (indexed by vertex) of the paths between the source vertex used to -compute the path state and a destination vertex `v`, a set of destination -vertices `vs`, or the entire graph. For multiple destination vertices, each +compute the path state and a single destination vertex, a list of destination +vertices, or the entire graph. For multiple destination vertices, each path is represented by a vector of vertices on the path between the source and the destination. Nonexistent paths will be indicated by an empty vector. For single destinations, the path is represented by a single vector of vertices, and will be length 0 if the path does not exist. +### Implementation Notes For Floyd-Warshall path states, please note that the output is a bit different, since this algorithm calculates all shortest paths for all pairs of vertices: `enumerate_paths(state)` will return a vector (indexed by source vertex) of diff --git a/src/shortestpaths/dijkstra.jl b/src/shortestpaths/dijkstra.jl index 8fb9c9057..a851dd081 100644 --- a/src/shortestpaths/dijkstra.jl +++ b/src/shortestpaths/dijkstra.jl @@ -1,53 +1,59 @@ -abstract AbstractDijkstraState<:AbstractPathState - -immutable DijkstraHeapEntry{T} - vertex::Int +struct DijkstraHeapEntry{T, U<:Integer} + vertex::U dist::T end isless(e1::DijkstraHeapEntry, e2::DijkstraHeapEntry) = e1.dist < e2.dist -type DijkstraState{T}<: AbstractDijkstraState - parents::Vector{Int} +""" + struct DijkstraState{T, U} + +An [`AbstractPathState`](@ref) designed for Dijkstra shortest-paths calculations. +""" +struct DijkstraState{T, U<:Integer}<: AbstractPathState + parents::Vector{U} dists::Vector{T} - predecessors::Vector{Vector{Int}} - pathcounts::Vector{Int} + predecessors::Vector{Vector{U}} + pathcounts::Vector{U} end -"""Performs [Dijkstra's algorithm](http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) -on a graph, computing shortest distances between a source vertex `s` and all -other nodes. Returns a `DijkstraState` that contains various traversal -information (see below). +""" + dijkstra_shortest_paths(g, srcs, distmx=DefaultDistance()); + +Perform [Dijkstra's algorithm](http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) +on a graph, computing shortest distances between `srcs` and all other vertices. +Return a [`DijkstraState`](@ref) that contains various traversal information. -With `allpaths=true`, returns a `DijkstraState` that keeps track of all -predecessors of a given vertex (see below). +### Optional Arguments +- `allpaths=false`: If true, returns a [`DijkstraState`](@ref) that keeps track of all +predecessors of a given vertex. """ -function dijkstra_shortest_paths{T}( +function dijkstra_shortest_paths( g::AbstractGraph, - srcs::Vector{Int}, - distmx::AbstractArray{T, 2}=DefaultDistance(); + srcs::Vector{U}, + distmx::AbstractMatrix{T}=DefaultDistance(); allpaths=false -) + ) where T where U<:Integer nvg = nv(g) dists = fill(typemax(T), nvg) - parents = zeros(Int, nvg) - preds = fill(Vector{Int}(),nvg) + parents = zeros(U, nvg) + preds = fill(Vector{U}(),nvg) visited = zeros(Bool, nvg) pathcounts = zeros(Int, nvg) - H = Vector{DijkstraHeapEntry{T}}() # this should be Vector{T}() in 0.4, I think. + H = Vector{DijkstraHeapEntry{T, U}}() # this should be Vector{T}() in 0.4, I think. dists[srcs] = zero(T) pathcounts[srcs] = 1 sizehint!(H, nvg) for v in srcs - heappush!(H, DijkstraHeapEntry{T}(v, dists[v])) + heappush!(H, DijkstraHeapEntry{T, U}(v, dists[v])) visited[v] = true end while !isempty(H) hentry = heappop!(H) - # info("Popped H - got $(hentry.vertex)") + # info("Popped H - got $(hentry.vertex)") u = hentry.vertex for v in out_neighbors(g,u) alt = (dists[u] == typemax(T))? typemax(T) : dists[u] + distmx[u,v] @@ -60,13 +66,13 @@ function dijkstra_shortest_paths{T}( if allpaths preds[v] = [u;] end - heappush!(H, DijkstraHeapEntry{T}(v, alt)) + heappush!(H, DijkstraHeapEntry{T, U}(v, alt)) # info("Pushed $v") else if alt < dists[v] dists[v] = alt parents[v] = u - heappush!(H, DijkstraHeapEntry{T}(v, alt)) + heappush!(H, DijkstraHeapEntry{T, U}(v, alt)) end if alt == dists[v] pathcounts[v] += pathcounts[u] @@ -84,8 +90,8 @@ function dijkstra_shortest_paths{T}( preds[src] = [] end - return DijkstraState{T}(parents, dists, preds, pathcounts) + return DijkstraState{T, U}(parents, dists, preds, pathcounts) end -dijkstra_shortest_paths{T}(g::AbstractGraph, src::Int, distmx::AbstractArray{T,2}=DefaultDistance(); allpaths=false) = - dijkstra_shortest_paths(g, [src;], distmx; allpaths=allpaths) +dijkstra_shortest_paths(g::AbstractGraph, src::Integer, distmx::AbstractMatrix = DefaultDistance(); allpaths=false) = +dijkstra_shortest_paths(g, [src;], distmx; allpaths=allpaths) diff --git a/src/shortestpaths/floyd-warshall.jl b/src/shortestpaths/floyd-warshall.jl index 582f68807..b7ddcb287 100644 --- a/src/shortestpaths/floyd-warshall.jl +++ b/src/shortestpaths/floyd-warshall.jl @@ -2,28 +2,34 @@ # licensing details. -type FloydWarshallState{T}<:AbstractPathState +""" + struct FloydWarshallState{T, U} + +An [`AbstractPathState`](@ref) designed for Floyd-Warshall shortest-paths calculations. +""" +struct FloydWarshallState{T, U<:Integer}<:AbstractPathState dists::Matrix{T} - parents::Matrix{Int} + parents::Matrix{U} end -doc"""Uses the [Floyd-Warshall algorithm](http://en.wikipedia.org/wiki/Floyd–Warshall_algorithm) -to compute shortest paths between all pairs of vertices in graph `g`. Returns a -`FloydWarshallState` with relevant traversal information, each is a -vertex-indexed vector of vectors containing the metric for each vertex in the -graph. +@doc_str """ +floyd_warshall_shortest_paths(g, distmx=DefaultDistance()) +Use the [Floyd-Warshall algorithm](http://en.wikipedia.org/wiki/Floyd–Warshall_algorithm) +to compute the shortest paths between all pairs of vertices in graph `g` using an +optional distance matrix `distmx`. Return a [`FloydWarshallState`](@ref) with relevant +traversal information. -Note that this algorithm may return a large amount of data (it will allocate -on the order of $\mathcal{O}(nv^2)$). +### Performance +Space complexity is on the order of ``\\mathcal{O}(|V|^2)``. """ function floyd_warshall_shortest_paths{T}( g::AbstractGraph, - distmx::AbstractArray{T, 2} = DefaultDistance() + distmx::AbstractMatrix{T} = DefaultDistance() ) - + U = eltype(g) n_v = nv(g) - dists = fill(typemax(T), (n_v,n_v)) - parents = zeros(Int, (n_v,n_v)) + dists = fill(typemax(T), (Int(n_v),Int(n_v))) + parents = zeros(U, (Int(n_v),Int(n_v))) # fws = FloydWarshallState(Matrix{T}(), Matrix{Int}()) for v in 1:n_v @@ -55,24 +61,17 @@ function floyd_warshall_shortest_paths{T}( end end fws = FloydWarshallState(dists, parents) - # for r in 1:size(parents,1) # row by row - # push!(fws.parents, vec(parents[r,:])) - # end - # for r in 1:size(dists,1) - # push!(fws.dists, vec(dists[r,:])) - # end - return fws end -function enumerate_paths(s::FloydWarshallState, v::Integer) +function enumerate_paths(s::FloydWarshallState{T, U}, v::Integer) where T where U<:Integer pathinfo = s.parents[v,:] - paths = Vector{Int}[] + paths = Vector{Vector{U}}() for i in 1:length(pathinfo) if i == v - push!(paths, Vector{Int}()) + push!(paths, Vector{U}()) else - path = Vector{Int}() + path = Vector{U}() currpathindex = i while currpathindex != 0 push!(path,currpathindex) diff --git a/src/spanningtrees/kruskal.jl b/src/spanningtrees/kruskal.jl index 61668968f..8cde79b8b 100644 --- a/src/spanningtrees/kruskal.jl +++ b/src/spanningtrees/kruskal.jl @@ -1,37 +1,42 @@ -immutable KruskalHeapEntry{T<:Real} +struct KruskalHeapEntry{T<:Real} edge::Edge dist::T end isless(e1::KruskalHeapEntry, e2::KruskalHeapEntry) = e1.dist < e2.dist -""" Performs [Quick-Find algorithm](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) -on a given pair of nodes `p`and `q`, and makes a connection between them -in the vector `nodes`. """ -function quick_find!(nodes, p, q) - pid = nodes[p] - qid = nodes[q] - for i in 1:length(nodes) - if nodes[i] == pid - nodes[i] = qid + quick_find!(vs, p, q) + +Perform [Quick-Find algorithm](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) +on a given pair of vertices `p`and `q`, and make a connection between them in the vector `vs`. +""" +function quick_find!(vs, p, q) + pid = vs[p] + qid = vs[q] + for i in 1:length(vs) + if vs[i] == pid + vs[i] = qid end end end -"""Performs [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm) -on a connected, non-directional graph `g`, having adjacency matrix `distmx`, -and computes minimum spanning tree. Returns a `Vector{KruskalHeapEntry}`, -that contains the containing edges and its weights. """ -function kruskal_mst{T<:Real}( - g::AbstractGraph, - distmx::AbstractArray{T, 2} = DefaultDistance() + kruskal_mst(g, distmx=DefaultDistance()) + +Return a vector of edges representing the minimum spanning tree of a connected, undirected graph `g` with optional +distance matrix `distmx` using [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm). +""" +function kruskal_mst end +@traitfn function kruskal_mst{T}( + g::::(!IsDirected), + distmx::AbstractMatrix{T} = DefaultDistance() ) + U = eltype(g) edge_list = Vector{KruskalHeapEntry{T}}() mst = Vector{Edge}() - connected_nodes = Vector{Int}(1:nv(g)) + connected_vs = collect(one(U):nv(g)) sizehint!(edge_list, ne(g)) sizehint!(mst, ne(g)) @@ -45,8 +50,8 @@ function kruskal_mst{T<:Real}( v = src(heap_entry.edge) w = dst(heap_entry.edge) - if connected_nodes[v] != connected_nodes[w] - quick_find!(connected_nodes, v, w) + if connected_vs[v] != connected_vs[w] + quick_find!(connected_vs, v, w) push!(mst, heap_entry.edge) end end diff --git a/src/spanningtrees/prim.jl b/src/spanningtrees/prim.jl index 55bc6da08..0d330cc4e 100644 --- a/src/spanningtrees/prim.jl +++ b/src/spanningtrees/prim.jl @@ -1,21 +1,23 @@ -immutable PrimHeapEntry{T<:Real} +struct PrimHeapEntry{T<:Real} edge::Edge dist::T end isless(e1::PrimHeapEntry, e2::PrimHeapEntry) = e1.dist < e2.dist -"""Performs [Prim's algorithm](https://en.wikipedia.org/wiki/Prim%27s_algorithm) -on a connected, non-directional graph `g`, having adjacency matrix `distmx`, -and computes minimum spanning tree. Returns a `Vector{Edge}`, -that contains the edges. """ -function prim_mst{T<:Real}( - g::AbstractGraph, - distmx::AbstractArray{T, 2} = DefaultDistance() - ) :: Vector{Edge} + prim_mst(g, distmx=DefaultDistance()) - pq = Vector{PrimHeapEntry{T}}() +Return a vector of edges representing the minimum spanning tree of a connected, undirected graph `g` with optional +distance matrix `distmx` using [Prim's algorithm](https://en.wikipedia.org/wiki/Prim%27s_algorithm). +Return a vector of edges. +""" +function prim_mst end +@traitfn function prim_mst( + g::::(!IsDirected), + distmx::AbstractMatrix = DefaultDistance() + ) + pq = Vector{PrimHeapEntry}() mst = Vector{Edge}() marked = zeros(Bool, nv(g)) @@ -39,18 +41,20 @@ function prim_mst{T<:Real}( end """ -Used to mark the visited vertices. Marks the vertex `v` of graph `g` true in the array `marked` -and enters all its edges into priority queue `pq` with its `distmx` values as a PrimHeapEntry. + visit!(g, v, marked, pq, distmx) + +Mark the vertex `v` of graph `g` true in the array `marked` and enter all its +edges into priority queue `pq` with its `distmx` values as a PrimHeapEntry. """ -function visit!{T<:Real}( +function visit!( g::AbstractGraph, - v::Int, - marked::AbstractArray{Bool, 1}, - pq::Vector{PrimHeapEntry{T}}, - distmx::AbstractArray{T, 2} + v::Integer, + marked::AbstractVector{Bool}, + pq::AbstractVector, + distmx::AbstractMatrix ) marked[v] = true - for w in fadj(g)[v] + for w in out_neighbors(g, v) if !marked[w] x = min(v, w) y = max(v, w) diff --git a/src/traversals/bfs.jl b/src/traversals/bfs.jl index b32d8e836..6682c0bdb 100644 --- a/src/traversals/bfs.jl +++ b/src/traversals/bfs.jl @@ -9,27 +9,28 @@ # ################################################# """ -**Conventions in Breadth First Search and Depth First Search** -VertexColorMap : + BreadthFirst + +## Conventions in Breadth First Search and Depth First Search +### VertexColorMap - color == 0 => unseen - color < 0 => examined but not closed - color > 0 => examined and closed -EdgeColorMap : +### EdgeColorMap - color == 0 => unseen -- color == 1 => examined +- color == 1 => examined """ - -type BreadthFirst <: AbstractGraphVisitAlgorithm -end +mutable struct BreadthFirst <: AbstractGraphVisitAlgorithm end function breadth_first_visit_impl!( - graph::AbstractGraph, # the graph - queue::Vector{Int}, # an (initialized) queue that stores the active vertices - vertexcolormap::AbstractVertexMap, # an (initialized) color-map to indicate status of vertices (-1=unseen, otherwise distance from root) - edgecolormap::AbstractEdgeMap, # an (initialized) color-map to indicate status of edges - visitor::AbstractGraphVisitor, # the visitor - dir::Symbol) # direction [:in,:out] + g::AbstractGraph, # the graph + queue::Vector, # an (initialized) queue that stores the active vertices + vertexcolormap::AbstractVertexMap, # an (initialized) color-map to indicate status of vertices (-1=unseen, otherwise distance from root) + edgecolormap::AbstractEdgeMap, # an (initialized) color-map to indicate status of edges + visitor::AbstractGraphVisitor, # the visitor + dir::Symbol # direction [:in,:out] + ) fneig = dir == :out ? out_neighbors : in_neighbors while !isempty(queue) @@ -37,7 +38,7 @@ function breadth_first_visit_impl!( open_vertex!(visitor, u) u_color = vertexcolormap[u] - for v in fneig(graph, u) + for v in fneig(g, u) v_color = get(vertexcolormap, v, 0) v_edge = Edge(u,v) e_color = get(edgecolormap, v_edge, 0) @@ -55,13 +56,13 @@ function breadth_first_visit_impl!( end function traverse_graph!( - graph::AbstractGraph, + g::AbstractGraph, alg::BreadthFirst, source, visitor::AbstractGraphVisitor; - vertexcolormap::AbstractVertexMap = Dict{Int, Int}(), + vertexcolormap::AbstractVertexMap = Dict{eltype(g), Int}(), edgecolormap::AbstractEdgeMap = DummyEdgeMap(), - queue = Vector{Int}(), + queue = Vector{eltype(g)}(), dir = :out) for s in source @@ -70,7 +71,7 @@ function traverse_graph!( push!(queue, s) end - breadth_first_visit_impl!(graph, queue, vertexcolormap, edgecolormap + breadth_first_visit_impl!(g, queue, vertexcolormap, edgecolormap , visitor, dir) end @@ -86,24 +87,30 @@ end # Constructing BFS trees # ########################################### -"""TreeBFSVisitorVector is a type for representing a BFS traversal -of the graph as a parents array. This type allows for a more performant implementation. """ -type TreeBFSVisitorVector <: AbstractGraphVisitor - tree::Vector{Int} + TreeBFSVisitorVector{T} + +A type for representing a BFS traversal of the graph as a parents array. +""" +mutable struct TreeBFSVisitorVector{T<:Integer} <: AbstractGraphVisitor + tree::Vector{T} end -function TreeBFSVisitorVector(n::Int) - return TreeBFSVisitorVector(fill(0, n)) +function TreeBFSVisitorVector(n::Integer) + return TreeBFSVisitorVector(fill(zero(n), n)) end -"""tree converts a parents array into a DiGraph""" -function tree(parents::AbstractVector) - n = length(parents) +""" + tree(parents) + +Convert a parents array into a directed graph. +""" +function tree(parents::AbstractVector{T}) where T<:Integer + n = T(length(parents)) t = DiGraph(n) - for i in 1:n + for i in one(T):n parent = parents[i] - if parent > 0 && parent != i + if parent > zero(T) && parent != i add_edge!(t, parent, i) end end @@ -112,7 +119,7 @@ end tree(parents::TreeBFSVisitorVector) = tree(parents.tree) -function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Int, v::Int, +function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) if u != v && vcolor == 0 visitor.tree[v] = u @@ -120,11 +127,11 @@ function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Int, v::Int, return true end -function bfs_tree!(visitor::TreeBFSVisitorVector, +function bfs_tree!(visitor::TreeBFSVisitorVector{T}, g::AbstractGraph, - s::Int; - vertexcolormap = Dict{Int,Int}(), - queue = Vector{Int}()) + s::Integer; + vertexcolormap = Dict{T,Int}(), + queue = Vector{T}()) where T<:Integer # this version of bfs_tree! allows one to reuse the memory necessary to compute the tree # the output is stored in the visitor.tree array whose entries are the vertex id of the # parent of the index. This function checks if the scratch space is too small for the graph. @@ -138,12 +145,16 @@ function bfs_tree!(visitor::TreeBFSVisitorVector, traverse_graph!(g, BreadthFirst(), s, visitor; vertexcolormap=vertexcolormap, queue=queue) end -"""Provides a breadth-first traversal of the graph `g` starting with source vertex `s`, -and returns a directed acyclic graph of vertices in the order they were discovered. +""" + bfs_tree(g, s) -This function is a high level wrapper around bfs_tree!, use that function for more performance. +Provide a breadth-first traversal of the graph `g` starting with source vertex `s`, +and return a directed acyclic graph of vertices in the order they were discovered. + +### Implementation Notes +This function is a high level wrapper around [`bfs_tree!`](@ref); use that function for more performance. """ -function bfs_tree(g::AbstractGraph, s::Int) +function bfs_tree(g::AbstractGraph, s::Integer) nvg = nv(g) visitor = TreeBFSVisitorVector(nvg) bfs_tree!(visitor, g, s) @@ -153,13 +164,18 @@ end ############################################ # Connected Components with BFS # ############################################ -"""Performing connected components with BFS starting from seed""" -type ComponentVisitorVector <: AbstractGraphVisitor - labels::Vector{Int} - seed::Int +""" + ComponentVisitorVector{T} + +A type of `AbstractGraphVisitor` that represents connected components with +BFS starting from a given seed. +""" +mutable struct ComponentVisitorVector{T<:Integer} <: AbstractGraphVisitor + labels::Vector{T} + seed::T end -function examine_neighbor!(visitor::ComponentVisitorVector, u::Int, v::Int, +function examine_neighbor!(visitor::ComponentVisitorVector, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) if u != v && vcolor == 0 visitor.labels[v] = visitor.seed @@ -170,14 +186,14 @@ end ############################################ # Test graph for bipartiteness # ############################################ -type BipartiteVisitor <: AbstractGraphVisitor +mutable struct BipartiteVisitor <: AbstractGraphVisitor bipartitemap::Vector{UInt8} is_bipartite::Bool end -BipartiteVisitor(n::Int) = BipartiteVisitor(zeros(UInt8,n), true) +BipartiteVisitor(n::Integer) = BipartiteVisitor(zeros(UInt8,n), true) -function examine_neighbor!(visitor::BipartiteVisitor, u::Int, v::Int, +function examine_neighbor!(visitor::BipartiteVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) if vcolor == 0 visitor.bipartitemap[v] = (visitor.bipartitemap[u] == 1) ? 2 : 1 @@ -190,26 +206,26 @@ function examine_neighbor!(visitor::BipartiteVisitor, u::Int, v::Int, end """ - is_bipartite(g) - is_bipartite(g, v) + is_bipartite(g[, v]) -Will return `true` if graph `g` is [bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). +Return `true` if graph `g` is [bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). If a node `v` is specified, only the connected component to which it belongs is considered. """ function is_bipartite(g::AbstractGraph) + T = eltype(g) cc = filter(x->length(x)>2, connected_components(g)) - vmap = Dict{Int,Int}() + vmap = Dict{T,Int}() for c in cc _is_bipartite(g,c[1], vmap=vmap) || return false end return true end -is_bipartite(g::AbstractGraph, v::Int) = _is_bipartite(g, v) +is_bipartite(g::AbstractGraph, v::Integer) = _is_bipartite(g, v) -_is_bipartite(g::AbstractGraph, v::Int; vmap = Dict{Int,Int}()) = _bipartite_visitor(g, v, vmap=vmap).is_bipartite +_is_bipartite(g::AbstractGraph, v::Integer; vmap = Dict{eltype(g),Int}()) = _bipartite_visitor(g, v, vmap=vmap).is_bipartite -function _bipartite_visitor(g::AbstractGraph, s::Int; vmap=Dict{Int,Int}()) +function _bipartite_visitor(g::AbstractGraph, s::Integer; vmap=Dict{eltype(g),Int}()) nvg = nv(g) visitor = BipartiteVisitor(nvg) for v in keys(vmap) #have to reset vmap, otherway problems with digraphs @@ -219,17 +235,19 @@ function _bipartite_visitor(g::AbstractGraph, s::Int; vmap=Dict{Int,Int}()) return visitor end -""" -If the graph is bipartite returns a vector `c` of size `nv(g)` containing -the assignment of each vertex to one of the two sets (`c[i] == 1` or `c[i]==2`). -If `g` is not bipartite returns an empty vector. +@doc_str """ + bipartite_map(g) + +For a bipartite graph `g`, return a vector `c` of size ``|V|`` containing +the assignment of each vertex to one of the two sets (``c_i == 1`` or c_i == 2``). +If `g` is not bipartite, return an empty vector. """ function bipartite_map(g::AbstractGraph) cc = connected_components(g) visitors = [_bipartite_visitor(g, x[1]) for x in cc] !all([v.is_bipartite for v in visitors]) && return zeros(Int, 0) m = zeros(Int, nv(g)) - for i=1:nv(g) + for i in vertices(g) m[i] = any(v->v.bipartitemap[i] == 1, visitors) ? 2 : 1 end m @@ -240,15 +258,17 @@ end ########################################### """ - gdistances!(g, source, dists) -> dists + gdistances!(g, source, dists) -Fills `dists` with the geodesic distances of vertices in `g` from vertex/vertices `source`. -`dists` should be a vector of length `nv(g)`. +Fill `dists` with the geodesic distances of vertices in `g` from `source`. +`dists` should be a vector of length `nv(g)`. Return `dists`. +For vertices in disconnected components the default distance is -1. """ function gdistances!(g::AbstractGraph, source, dists) + T = eltype(g) n = nv(g) fill!(dists, -1) - queue = Vector{Int}(n) + queue = Vector{T}(n) for i in 1:length(source) queue[i] = source[i] dists[source[i]] = 0 @@ -259,7 +279,7 @@ function gdistances!(g::AbstractGraph, source, dists) current = queue[head] distance = dists[current] + 1 head += 1 - for j in fadj(g, current) + for j in out_neighbors(g, current) if dists[j] == -1 dists[j] = distance tail += 1 @@ -272,11 +292,10 @@ end """ - gdistances(g, source) -> dists + gdistances(g, source) -Returns a vector filled with the geodesic distances of vertices in `g` from vertex/vertices `source`. -If `source` is a collection of vertices they should be unique (not checked). +Return a vector filled with the geodesic distances of vertices in `g` from +`source`. If `source` is a collection of vertices each element should be unique. For vertices in disconnected components the default distance is -1. """ gdistances(g::AbstractGraph, source) = gdistances!(g, source, Vector{Int}(nv(g))) - diff --git a/src/traversals/dfs.jl b/src/traversals/dfs.jl index 011c1e157..723c3fec7 100644 --- a/src/traversals/dfs.jl +++ b/src/traversals/dfs.jl @@ -10,22 +10,21 @@ # ################################################# """ -**Conventions in Breadth First Search and Depth First Search** -VertexColorMap : + DepthFirst +## Conventions in Breadth First Search and Depth First Search +### VertexColorMap - color == 0 => unseen - color < 0 => examined but not closed - color > 0 => examined and closed -EdgeColorMap : +### EdgeColorMap - color == 0 => unseen - color == 1 => examined """ - -type DepthFirst <: AbstractGraphVisitAlgorithm -end +mutable struct DepthFirst <: AbstractGraphVisitAlgorithm end function depth_first_visit_impl!( - graph::AbstractGraph, # the graph + g::AbstractGraph, # the graph stack, # an (initialized) stack of vertex vertexcolormap::AbstractVertexMap, # an (initialized) color-map to indicate status of vertices edgecolormap::AbstractEdgeMap, # an (initialized) color-map to indicate status of edges @@ -53,7 +52,7 @@ function depth_first_visit_impl!( push!(stack, (u, udsts, tstate)) open_vertex!(visitor, v) - vdsts = fadj(graph, v) + vdsts = out_neighbors(g, v) push!(stack, (v, vdsts, start(vdsts))) end end @@ -66,21 +65,22 @@ function depth_first_visit_impl!( end function traverse_graph!( - graph::AbstractGraph, + g::AbstractGraph, alg::DepthFirst, - s::Int, + s::Integer, visitor::AbstractGraphVisitor; - vertexcolormap = Dict{Int, Int}(), + vertexcolormap = Dict{eltype(g), Int}(), edgecolormap = DummyEdgeMap()) + T = eltype(g) vertexcolormap[s] = -1 discover_vertex!(visitor, s) || return - sdsts = fadj(graph, s) + sdsts = out_neighbors(g, s) sstate = start(sdsts) - stack = [(s, sdsts, sstate)] + stack = [(T(s), sdsts, sstate)] - depth_first_visit_impl!(graph, stack, vertexcolormap, edgecolormap, visitor) + depth_first_visit_impl!(g, stack, vertexcolormap, edgecolormap, visitor) end ################################################# @@ -91,16 +91,15 @@ end # Test whether a graph is cyclic -type DFSCyclicTestVisitor <: AbstractGraphVisitor +mutable struct DFSCyclicTestVisitor <: AbstractGraphVisitor found_cycle::Bool - DFSCyclicTestVisitor() = new(false) end function examine_neighbor!( vis::DFSCyclicTestVisitor, - u::Int, - v::Int, + u::Integer, + v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) @@ -115,8 +114,10 @@ discover_vertex!(vis::DFSCyclicTestVisitor, v) = !vis.found_cycle """ is_cyclic(g) -Tests whether a graph contains a cycle through depth-first search. It -returns `true` when it finds a cycle, otherwise `false`. +Return `true` if graph `g` contains a cycle. + +### Implementation Notes +Uses DFS. """ function is_cyclic(g::AbstractGraph) cmap = zeros(Int, nv(g)) @@ -133,33 +134,32 @@ end # Topological sort using DFS -type TopologicalSortVisitor <: AbstractGraphVisitor - vertices::Vector{Int} - - function TopologicalSortVisitor(n::Int) - vs = Array(Int, 0) - sizehint!(vs, n) - new(vs) - end +mutable struct TopologicalSortVisitor{T} <: AbstractGraphVisitor + vertices::Vector{T} end +function TopologicalSortVisitor(n::T) where T<:Integer + vs = Vector{T}() + sizehint!(vs, n) + return TopologicalSortVisitor(vs) +end -function examine_neighbor!(visitor::TopologicalSortVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(visitor::TopologicalSortVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) (vcolor < 0 && ecolor == 0) && error("The input graph contains at least one loop.") end -function close_vertex!(visitor::TopologicalSortVisitor, v::Int) +function close_vertex!(visitor::TopologicalSortVisitor, v::Integer) push!(visitor.vertices, v) end -function topological_sort_by_dfs(graph::AbstractGraph) - nvg = nv(graph) +function topological_sort_by_dfs(g::AbstractGraph) + nvg = nv(g) cmap = zeros(Int, nvg) visitor = TopologicalSortVisitor(nvg) - for s in vertices(graph) + for s in vertices(g) if cmap[s] == 0 - traverse_graph!(graph, DepthFirst(), s, visitor, vertexcolormap=cmap) + traverse_graph!(g, DepthFirst(), s, visitor, vertexcolormap=cmap) end end @@ -167,14 +167,14 @@ function topological_sort_by_dfs(graph::AbstractGraph) end -type TreeDFSVisitor <:AbstractGraphVisitor +mutable struct TreeDFSVisitor{T} <:AbstractGraphVisitor tree::DiGraph - predecessor::Vector{Int} + predecessor::Vector{T} end -TreeDFSVisitor(n::Int) = TreeDFSVisitor(DiGraph(n), zeros(Int,n)) +TreeDFSVisitor(n::T) where T<:Integer = TreeDFSVisitor(DiGraph(n), zeros(T,n)) -function examine_neighbor!(visitor::TreeDFSVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(visitor::TreeDFSVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) if (vcolor == 0) visitor.predecessor[v] = u end @@ -182,12 +182,12 @@ function examine_neighbor!(visitor::TreeDFSVisitor, u::Int, v::Int, ucolor::Int, end """ - dfs_tree(g, s::Int) + dfs_tree(g, s) -Provides a depth-first traversal of the graph `g` starting with source vertex `s`, -and returns a directed acyclic graph of vertices in the order they were discovered. +Return an ordered vector of vertices representing a directed acylic graph based on +depth-first traversal of the graph `g` starting with source vertex `s`. """ -function dfs_tree(g::AbstractGraph, s::Int) +function dfs_tree(g::AbstractGraph, s::Integer) nvg = nv(g) visitor = TreeDFSVisitor(nvg) traverse_graph!(g, DepthFirst(), s, visitor) diff --git a/src/traversals/graphvisit.jl b/src/traversals/graphvisit.jl index 002f6eb4f..17f1c120a 100644 --- a/src/traversals/graphvisit.jl +++ b/src/traversals/graphvisit.jl @@ -3,7 +3,7 @@ # The concept and trivial implementation of graph visitors -abstract AbstractGraphVisitor +abstract type AbstractGraphVisitor end # trivial implementation @@ -21,17 +21,17 @@ examine_neighbor!(vis::AbstractGraphVisitor, u, v, ucolor::Int, vcolor::Int, eco close_vertex!(vis::AbstractGraphVisitor, v) = true -type TrivialGraphVisitor <: AbstractGraphVisitor +struct TrivialGraphVisitor <: AbstractGraphVisitor end # This is the common base for BreadthFirst and DepthFirst -abstract AbstractGraphVisitAlgorithm +abstract type AbstractGraphVisitAlgorithm end -typealias AbstractEdgeMap{T} Associative{Edge,T} -typealias AbstractVertexMap{T} Union{AbstractVector{T},Associative{Int, T}} +const AbstractEdgeMap{T} = Associative{Edge,T} +const AbstractVertexMap{T<:Integer, U} = Union{AbstractVector{T},Associative{T, U}} -type DummyEdgeMap <: AbstractEdgeMap{Int} +struct DummyEdgeMap <: AbstractEdgeMap{Int} end getindex(d::DummyEdgeMap, e::Edge) = 0 @@ -47,54 +47,54 @@ get(d::DummyEdgeMap, e::Edge, x::Int) = x # List vertices by the order of being discovered -type VertexListVisitor <: AbstractGraphVisitor - vertices::Vector{Int} +struct VertexListVisitor{T<:Integer} <: AbstractGraphVisitor + vertices::Vector{T} +end - function VertexListVisitor(n::Integer=0) - vs = Vector{Int}() - sizehint!(vs, n) - new(vs) - end +function VertexListVisitor(n::T=0) where T<:Integer + vs = Vector{T}() + sizehint!(vs, n) + return VertexListVisitor(vs) end -function discover_vertex!(visitor::VertexListVisitor, v::Int) +function discover_vertex!(visitor::VertexListVisitor, v::Integer) push!(visitor.vertices, v) return true end function visited_vertices( - graph::AbstractGraph, + g::AbstractGraph, alg::AbstractGraphVisitAlgorithm, sources) - - visitor = VertexListVisitor(nv(graph)) - traverse_graph!(graph, alg, sources, visitor) - visitor.vertices::Vector{Int} + T = eltype(g) + visitor = VertexListVisitor(nv(g)) + traverse_graph!(g, alg, sources, visitor) + visitor.vertices::Vector{T} end # Print visit log -type LogGraphVisitor{S<:IO} <: AbstractGraphVisitor +struct LogGraphVisitor{S<:IO} <: AbstractGraphVisitor io::S end -function discover_vertex!(vis::LogGraphVisitor, v::Int) +function discover_vertex!(vis::LogGraphVisitor, v::Integer) println(vis.io, "discover vertex: $v") return true end -function open_vertex!(vis::LogGraphVisitor, v::Int) +function open_vertex!(vis::LogGraphVisitor, v::Integer) println(vis.io, "open vertex: $v") return true end -function close_vertex!(vis::LogGraphVisitor, v::Int) +function close_vertex!(vis::LogGraphVisitor, v::Integer) println(vis.io, "close vertex: $v") return true end -function examine_neighbor!(vis::LogGraphVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(vis::LogGraphVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) println(vis.io, "examine neighbor: $u -> $v (ucolor = $ucolor, vcolor = $vcolor, edgecolor= $ecolor)") return true end diff --git a/src/traversals/maxadjvisit.jl b/src/traversals/maxadjvisit.jl index b8660e11b..6ff65664f 100644 --- a/src/traversals/maxadjvisit.jl +++ b/src/traversals/maxadjvisit.jl @@ -10,13 +10,13 @@ # ################################################# -type MaximumAdjacency <: AbstractGraphVisitAlgorithm +struct MaximumAdjacency <: AbstractGraphVisitAlgorithm end -abstract AbstractMASVisitor <: AbstractGraphVisitor +abstract type AbstractMASVisitor <: AbstractGraphVisitor end function maximum_adjacency_visit_impl!{T}( - graph::AbstractGraph, # the graph + g::AbstractGraph, # the graph pq::DataStructures.PriorityQueue{Int, T}, # priority queue visitor::AbstractMASVisitor, # the visitor colormap::Vector{Int}) # traversal status @@ -24,7 +24,7 @@ function maximum_adjacency_visit_impl!{T}( while !isempty(pq) u = DataStructures.dequeue!(pq) discover_vertex!(visitor, u) - for v in fadj(graph, u) + for v in out_neighbors(g, u) examine_neighbor!(visitor, u, v, 0, 0, 0) if haskey(pq,v) @@ -38,10 +38,10 @@ function maximum_adjacency_visit_impl!{T}( end function traverse_graph!( - graph::AbstractGraph, + g::AbstractGraph, T::DataType, alg::MaximumAdjacency, - s::Int, + s::Integer, visitor::AbstractMASVisitor, colormap::Vector{Int}) @@ -49,18 +49,18 @@ function traverse_graph!( pq = DataStructures.PriorityQueue(Int,T,Base.Order.Reverse) # Set number of visited neighbors for all vertices to 0 - for v in vertices(graph) + for v in vertices(g) pq[v] = zero(T) end @assert haskey(pq,s) - @assert nv(graph) >= 2 + @assert nv(g) >= 2 #Give the starting vertex high priority pq[s] = one(T) #start traversing the graph - maximum_adjacency_visit_impl!(graph, pq, visitor, colormap) + maximum_adjacency_visit_impl!(g, pq, visitor, colormap) end @@ -77,40 +77,41 @@ end # ################################################# -type MinCutVisitor{T} <: AbstractMASVisitor +mutable struct MinCutVisitor{T, U<:Integer} <: AbstractMASVisitor graph::AbstractGraph - parities::AbstractArray{Bool,1} + parities::BitVector colormap::Vector{Int} bestweight::T cutweight::T - visited::Integer - distmx::AbstractArray{T, 2} - vertices::Vector{Int} + visited::Int + distmx::AbstractMatrix{T} + vertices::Vector{U} end -function MinCutVisitor{T}(graph::AbstractGraph, distmx::AbstractArray{T, 2}) - n = nv(graph) +function MinCutVisitor{T}(g::AbstractGraph, distmx::AbstractMatrix{T}) + U = eltype(g) + n = nv(g) parities = falses(n) return MinCutVisitor( - graph, + g, falses(n), zeros(Int,n), typemax(T), zero(T), zero(Int), distmx, - Vector{Int}() + Vector{U}() ) end -function discover_vertex!(vis::MinCutVisitor, v::Int) +function discover_vertex!(vis::MinCutVisitor, v::Integer) vis.parities[v] = false vis.colormap[v] = 1 push!(vis.vertices,v) return true end -function examine_neighbor!(vis::MinCutVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(vis::MinCutVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) ew = vis.distmx[u, v] # if the target of e is already marked then decrease cutweight @@ -124,7 +125,7 @@ function examine_neighbor!(vis::MinCutVisitor, u::Int, v::Int, ucolor::Int, vcol return true end -function close_vertex!(vis::MinCutVisitor, v::Int) +function close_vertex!(vis::MinCutVisitor, v::Integer) vis.colormap[v] = 2 vis.visited += 1 @@ -143,25 +144,25 @@ end # ################################################# -type MASVisitor{T} <: AbstractMASVisitor +struct MASVisitor{T, U<:Integer} <: AbstractMASVisitor io::IO - vertices::Vector{Int} - distmx::AbstractArray{T, 2} + vertices::Vector{U} + distmx::AbstractMatrix{T} log::Bool end -function discover_vertex!{T}(visitor::MASVisitor{T}, v::Int) +function discover_vertex!{T}(visitor::MASVisitor{T}, v::Integer) push!(visitor.vertices,v) visitor.log ? println(visitor.io, "discover vertex: $v") : nothing return true end -function examine_neighbor!(visitor::MASVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(visitor::MASVisitor, u::Integer, v::Integer, ucolor::Int, vcolor::Int, ecolor::Int) visitor.log ? println(visitor.io, " -- examine neighbor from $u to $v") : nothing return true end -function close_vertex!(visitor::MASVisitor, v::Int) +function close_vertex!(visitor::MASVisitor, v::Integer) visitor.log ? println(visitor.io, "close vertex: $v") : nothing return true end @@ -173,42 +174,47 @@ end ################################################# -"""Returns a tuple `(parity, bestcut)`, where `parity` is a vector of integer +""" + mincut(g, distmx=DefaultDistance()) + +Return a tuple `(parity, bestcut)`, where `parity` is a vector of integer values that determines the partition in `g` (1 or 2) and `bestcut` is the weight of the cut that makes this partition. An optional `distmx` matrix may be specified; if omitted, edge distances are assumed to be 1. """ function mincut{T}( - graph::AbstractGraph, - distmx::AbstractArray{T, 2} + g::AbstractGraph, + distmx::AbstractMatrix{T}=DefaultDistance() ) - visitor = MinCutVisitor(graph, distmx) - colormap = zeros(Int, nv(graph)) - traverse_graph!(graph, T, MaximumAdjacency(), 1, visitor, colormap) + visitor = MinCutVisitor(g, distmx) + colormap = zeros(Int, nv(g)) + traverse_graph!(g, T, MaximumAdjacency(), 1, visitor, colormap) return(visitor.parities + 1, visitor.bestweight) end -mincut(graph::AbstractGraph) = mincut(graph,DefaultDistance()) -"""Returns the vertices in `g` traversed by maximum adjacency search. An optional +""" + maximum_adjacency_visit(g[, distmx][, log][, io]) + +Return the vertices in `g` traversed by maximum adjacency search. An optional `distmx` matrix may be specified; if omitted, edge distances are assumed to be 1. If `log` (default `false`) is `true`, visitor events will be printed to `io`, which defaults to `STDOUT`; otherwise, no event information will be displayed. """ function maximum_adjacency_visit{T}( - graph::AbstractGraph, - distmx::AbstractArray{T, 2}, + g::AbstractGraph, + distmx::AbstractMatrix{T}, log::Bool, io::IO ) visitor = MASVisitor(io, Vector{Int}(), distmx, log) - traverse_graph!(graph, T, MaximumAdjacency(), 1, visitor, zeros(Int, nv(graph))) + traverse_graph!(g, T, MaximumAdjacency(), 1, visitor, zeros(Int, nv(g))) return visitor.vertices end -maximum_adjacency_visit(graph::AbstractGraph) = maximum_adjacency_visit( - graph, +maximum_adjacency_visit(g::AbstractGraph) = maximum_adjacency_visit( + g, DefaultDistance(), false, STDOUT diff --git a/src/traversals/randomwalks.jl b/src/traversals/randomwalks.jl index 8f4631b89..148512f2e 100644 --- a/src/traversals/randomwalks.jl +++ b/src/traversals/randomwalks.jl @@ -1,28 +1,38 @@ -"""Performs a random walk on graph `g` starting at vertex `s` and continuing for -a maximum of `niter` steps. Returns a vector of vertices visited in order. +""" + randomwalk(g, s, niter) + +Perform a random walk on graph `g` starting at vertex `s` and continuing for +a maximum of `niter` steps. Return a vector of vertices visited in order. """ function randomwalk(g::AbstractGraph, s::Integer, niter::Integer) - s in vertices(g) || throw(BoundsError()) - visited = Vector{Int}() - sizehint!(visited, niter) - currs = s - i = 1 - while i <= niter - push!(visited, currs) - i += 1 - nbrs = out_neighbors(g,currs) - length(nbrs) == 0 && break - currs = rand(nbrs) - end - return visited[1:i-1] + T = eltype(g) + s in vertices(g) || throw(BoundsError()) + visited = Vector{T}() + sizehint!(visited, niter) + currs = s + i = 1 + while i <= niter + push!(visited, currs) + i += 1 + nbrs = out_neighbors(g,currs) + length(nbrs) == 0 && break + currs = rand(nbrs) + end + return visited[1:i-1] end -"""Performs a non-backtracking random walk on graph `g` starting at vertex `s` and continuing for -a maximum of `niter` steps. Returns a vector of vertices visited in order. """ -function non_backtracking_randomwalk(g::Graph, s::Integer, niter::Integer) + non_backtracking_randomwalk(g, s, niter) + +Perform a non-backtracking random walk on directed graph `g` starting at +vertex `s` and continuing for a maximum of `niter` steps. Return a +vector of vertices visited in order. +""" +function non_backtracking_randomwalk end +@traitfn function non_backtracking_randomwalk(g::::(!IsDirected), s::Integer, niter::Integer) + T = eltype(g) s in vertices(g) || throw(BoundsError()) - visited = Vector{Int}() + visited = Vector{T}() sizehint!(visited, niter) currs = s prev = -1 @@ -51,9 +61,10 @@ function non_backtracking_randomwalk(g::Graph, s::Integer, niter::Integer) return visited[1:i-1] end -function non_backtracking_randomwalk(g::DiGraph, s::Integer, niter::Integer) +@traitfn function non_backtracking_randomwalk(g::::IsDirected, s::Integer, niter::Integer) + T = eltype(g) s in vertices(g) || throw(BoundsError()) - visited = Vector{Int}() + visited = Vector{T}() sizehint!(visited, niter) currs = s prev = -1 @@ -79,25 +90,28 @@ function non_backtracking_randomwalk(g::DiGraph, s::Integer, niter::Integer) return visited[1:i-1] end -"""Performs a [self-avoiding walk](https://en.wikipedia.org/wiki/Self-avoiding_walk) +""" + saw(g, s, niter) +Perform a [self-avoiding walk](https://en.wikipedia.org/wiki/Self-avoiding_walk) on graph `g` starting at vertex `s` and continuing for a maximum of `niter` steps. -Returns a vector of vertices visited in order. +Return a vector of vertices visited in order. """ function saw(g::AbstractGraph, s::Integer, niter::Integer) - s in vertices(g) || throw(BoundsError()) - visited = Vector{Int}() - svisited = Set{Int}() - sizehint!(visited, niter) - sizehint!(svisited, niter) - currs = s - i = 1 - while i <= niter - push!(visited, currs) - push!(svisited, currs) - i += 1 - nbrs = setdiff(Set(out_neighbors(g,currs)),svisited) - length(nbrs) == 0 && break - currs = rand(collect(nbrs)) - end - return visited[1:i-1] + T = eltype(g) + s in vertices(g) || throw(BoundsError()) + visited = Vector{T}() + svisited = Set{T}() + sizehint!(visited, niter) + sizehint!(svisited, niter) + currs = s + i = 1 + while i <= niter + push!(visited, currs) + push!(svisited, currs) + i += 1 + nbrs = setdiff(Set(out_neighbors(g,currs)),svisited) + length(nbrs) == 0 && break + currs = rand(collect(nbrs)) + end + return visited[1:i-1] end diff --git a/src/utils.jl b/src/utils.jl index e70dce06e..7e5e5c4cc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,8 +1,13 @@ """ -sample!([rng,] a, k; exclude = ()) +sample!([rng, ]a, k) Sample `k` element from array `a` without repetition and eventually excluding elements in `exclude`. -Pay attention, it changes the order of the elements in `a`. + +### Optional Arguments +- `exclude=()`: elements in `a` to exclude from sampling. + +### Implementation Notes +Changes the order of the elements in `a`. For a non-mutating version, see [`sample`](@ref). """ function sample!(rng::AbstractRNG, a::AbstractArray, k::Integer; exclude = ()) length(a) < k + length(exclude) && error("Array too short.") @@ -24,9 +29,15 @@ end sample!(a::AbstractArray, k::Integer; exclude = ()) = sample!(getRNG(), a, k; exclude = exclude) """ -sample([rng,] r, k; exclude = ()) + sample([rng,] r, k) + Sample `k` element from unit range `r` without repetition and eventually excluding elements in `exclude`. -Unlike `sample!`, does not produce side effects. + +### Optional Arguments +- `exclude=()`: elements in `a` to exclude from sampling. + +### Implementation Notes +Unlike [`sample!`](@ref), does not produce side effects. """ sample(a::UnitRange, k::Integer; exclude = ()) = sample!(getRNG(), collect(a), k; exclude = exclude) diff --git a/test/biconnectivity/articulation.jl b/test/biconnectivity/articulation.jl index 079ab151d..78a7cdcc8 100644 --- a/test/biconnectivity/articulation.jl +++ b/test/biconnectivity/articulation.jl @@ -1,34 +1,39 @@ @testset "Articulation" begin - g = Graph(13) - add_edge!(g, 1, 7) - add_edge!(g, 1, 2) - add_edge!(g, 1, 3) - add_edge!(g, 12, 13) - add_edge!(g, 10, 13) - add_edge!(g, 10, 12) - add_edge!(g, 12, 11) - add_edge!(g, 5, 4) - add_edge!(g, 6, 4) - add_edge!(g, 8, 9) - add_edge!(g, 6, 5) - add_edge!(g, 1, 6) - add_edge!(g, 7, 5) - add_edge!(g, 7, 3) - add_edge!(g, 7, 8) - add_edge!(g, 7, 10) - add_edge!(g, 7, 12) - - art = articulation(g) - ans = [1, 7, 8, 12] - @test art == ans + gint = Graph(13) + add_edge!(gint, 1, 7) + add_edge!(gint, 1, 2) + add_edge!(gint, 1, 3) + add_edge!(gint, 12, 13) + add_edge!(gint, 10, 13) + add_edge!(gint, 10, 12) + add_edge!(gint, 12, 11) + add_edge!(gint, 5, 4) + add_edge!(gint, 6, 4) + add_edge!(gint, 8, 9) + add_edge!(gint, 6, 5) + add_edge!(gint, 1, 6) + add_edge!(gint, 7, 5) + add_edge!(gint, 7, 3) + add_edge!(gint, 7, 8) + add_edge!(gint, 7, 10) + add_edge!(gint, 7, 12) + for g in testgraphs(gint) + art = @inferred(articulation(g)) + ans = [1, 7, 8, 12] + @test art == ans + end for level in 1:6 - tree = LightGraphs.BinaryTree(level) - artpts = articulation(tree) - @test artpts == collect(1:(2^(level-1)-1)) + btree = LightGraphs.BinaryTree(level) + for tree in [btree, Graph{UInt8}(btree), Graph{Int16}(btree)] + artpts = @inferred(articulation(tree)) + @test artpts == collect(1:(2^(level-1)-1)) + end end - h = LightGraphs.blkdiag(WheelGraph(5), WheelGraph(5)) - add_edge!(h, 5,6) - @test articulation(h) == [5,6] + hint = LightGraphs.blkdiag(WheelGraph(5), WheelGraph(5)) + add_edge!(hint, 5, 6) + for h in (hint, Graph{UInt8}(hint), Graph{Int16}(hint)) + @test @inferred(articulation(h)) == [5, 6] + end end diff --git a/test/biconnectivity/biconnect.jl b/test/biconnectivity/biconnect.jl index b4b7bc754..44a021b44 100644 --- a/test/biconnectivity/biconnect.jl +++ b/test/biconnectivity/biconnect.jl @@ -1,27 +1,30 @@ @testset "Biconnect" begin - g = Graph(12) - add_edge!(g, 1, 2) - add_edge!(g, 2, 3) - add_edge!(g, 2, 4) - add_edge!(g, 3, 4) - add_edge!(g, 3, 5) - add_edge!(g, 4, 5) - add_edge!(g, 2, 6) - add_edge!(g, 1, 7) - add_edge!(g, 6, 7) - add_edge!(g, 6, 8) - add_edge!(g, 6, 9) - add_edge!(g, 8, 9) - add_edge!(g, 9, 10) - add_edge!(g, 11, 12) + gint = Graph(12) + add_edge!(gint, 1, 2) + add_edge!(gint, 2, 3) + add_edge!(gint, 2, 4) + add_edge!(gint, 3, 4) + add_edge!(gint, 3, 5) + add_edge!(gint, 4, 5) + add_edge!(gint, 2, 6) + add_edge!(gint, 1, 7) + add_edge!(gint, 6, 7) + add_edge!(gint, 6, 8) + add_edge!(gint, 6, 9) + add_edge!(gint, 8, 9) + add_edge!(gint, 9, 10) + add_edge!(gint, 11, 12) - bcc = biconnected_components(g) a = [[Edge(3, 5), Edge(4, 5), Edge(2, 4), Edge(3, 4), Edge(2, 3)], [Edge(9, 10)], [Edge(6, 9), Edge(8, 9), Edge(6, 8)], [Edge(1, 7), Edge(6, 7), Edge(2, 6), Edge(1, 2)], [Edge(11, 12)]] - @test bcc == a + + for g in testgraphs(gint) + bcc = @inferred(biconnected_components(g)) + @test bcc == a + end g = Graph(4) add_edge!(g, 1, 2) @@ -35,10 +38,13 @@ add_edge!(h, 3, 4) add_edge!(h, 1, 4) - G = blkdiag(g, h) - add_edge!(G, 4, 5) + gint = blkdiag(g, h) + add_edge!(gint, 4, 5) - bcc = biconnected_components(G) 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)]] - @test bcc == a + + for g in testgraphs(gint) + bcc = @inferred(biconnected_components(g)) + @test bcc == a + end end diff --git a/test/centrality/betweenness.jl b/test/centrality/betweenness.jl index 2a922ab63..e25f38672 100644 --- a/test/centrality/betweenness.jl +++ b/test/centrality/betweenness.jl @@ -1,4 +1,10 @@ @testset "Betweenness" begin + # self loops + s2 = DiGraph(3) + add_edge!(s2,1,2); add_edge!(s2,2,3); add_edge!(s2,3,3) + s1 = Graph(s2) + g3 = PathGraph(5) + function readcentrality(f::AbstractString) f = open(f,"r") c = Vector{Float64}() @@ -10,33 +16,34 @@ end - g = load(joinpath(testdir,"testdata","graph-50-500.jgz"), "graph-50-500") + gint = load(joinpath(testdir,"testdata","graph-50-500.jgz"), "graph-50-500") c = readcentrality(joinpath(testdir,"testdata","graph-50-500-bc.txt")) - z = betweenness_centrality(g) - - @test map(Float32, z) == map(Float32, c) + for g in testdigraphs(gint) + z = @inferred(betweenness_centrality(g)) + @test map(Float32, z) == map(Float32, c) - y = betweenness_centrality(g, endpoints=true, normalize=false) - @test round(y[1:3],4) == - round([122.10760591498584, 159.0072453120582, 176.39547945994505], 4) + y = @inferred(betweenness_centrality(g, endpoints=true, normalize=false)) + @test round.(y[1:3],4) == + round.([122.10760591498584, 159.0072453120582, 176.39547945994505], 4) - x = betweenness_centrality(g,3) - @test length(x) == 50 + x = @inferred(betweenness_centrality(g,3)) + @test length(x) == 50 + end - @test betweenness_centrality(s1) == [0, 1, 0] - @test betweenness_centrality(s2) == [0, 0.5, 0] + @test @inferred(betweenness_centrality(s1)) == [0, 1, 0] + @test @inferred(betweenness_centrality(s2)) == [0, 0.5, 0] g = Graph(2) add_edge!(g,1,2) - z = betweenness_centrality(g; normalize=true) + z = @inferred(betweenness_centrality(g; normalize=true)) @test z[1] == z[2] == 0.0 - z2 = betweenness_centrality(g, vertices(g)) - z3 = betweenness_centrality(g, [vertices(g);]) + z2 = @inferred(betweenness_centrality(g, vertices(g))) + z3 = @inferred(betweenness_centrality(g, [vertices(g);])) @test z == z2 == z3 - z = betweenness_centrality(g3; normalize=false) + z = @inferred(betweenness_centrality(g3; normalize=false)) @test z[1] == z[5] == 0.0 end diff --git a/test/centrality/closeness.jl b/test/centrality/closeness.jl index b10e6bb27..4cda3e586 100644 --- a/test/centrality/closeness.jl +++ b/test/centrality/closeness.jl @@ -1,13 +1,18 @@ @testset "Closeness" begin - y = closeness_centrality(g5; normalize=false) - z = closeness_centrality(g5) + g5 = DiGraph(4) + add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) + for g in testdigraphs(g5) + y = @inferred(closeness_centrality(g; normalize=false)) + z = @inferred(closeness_centrality(g)) + @test y == [0.75, 0.6666666666666666, 1.0, 0.0] + @test z == [0.75, 0.4444444444444444, 0.3333333333333333, 0.0] + end - @test y == [0.75, 0.6666666666666666, 1.0, 0.0] - @test z == [0.75, 0.4444444444444444, 0.3333333333333333, 0.0] - - g = Graph(5) - add_edge!(g,1,2) - z = closeness_centrality(g) - @test z[1] == z[2] == 0.25 - @test z[3] == z[4] == z[5] == 0.0 + g5 = Graph(5) + add_edge!(g5,1,2) + for g in testgraphs(g5) + z = @inferred(closeness_centrality(g)) + @test z[1] == z[2] == 0.25 + @test z[3] == z[4] == z[5] == 0.0 + end end diff --git a/test/centrality/degree.jl b/test/centrality/degree.jl index ce1f5a876..837b847a2 100644 --- a/test/centrality/degree.jl +++ b/test/centrality/degree.jl @@ -1,5 +1,9 @@ @testset "Degree" begin - @test degree_centrality(g5) == [0.6666666666666666, 0.6666666666666666, 1.0, 0.3333333333333333] - @test indegree_centrality(g5, normalize=false) == [0.0, 1.0, 2.0, 1.0] - @test outdegree_centrality(g5; normalize=false) == [2.0, 1.0, 1.0, 0.0] + g5 = DiGraph(4) + add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) + for g in testdigraphs(g5) + @test @inferred(degree_centrality(g)) == [0.6666666666666666, 0.6666666666666666, 1.0, 0.3333333333333333] + @test @inferred(indegree_centrality(g, normalize=false)) == [0.0, 1.0, 2.0, 1.0] + @test @inferred(outdegree_centrality(g; normalize=false)) == [2.0, 1.0, 1.0, 0.0] + end end diff --git a/test/centrality/katz.jl b/test/centrality/katz.jl index b933cd6e9..fe83ef29a 100644 --- a/test/centrality/katz.jl +++ b/test/centrality/katz.jl @@ -1,4 +1,8 @@ @testset "Katz" begin - z = katz_centrality(g5, 0.4) - @test round(z, 2) == [0.32, 0.44, 0.62, 0.56] + g5 = DiGraph(4) + add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) + for g in testdigraphs(g5) + z = @inferred(katz_centrality(g, 0.4)) + @test round.(z, 2) == [0.32, 0.44, 0.62, 0.56] + end end diff --git a/test/centrality/pagerank.jl b/test/centrality/pagerank.jl index ba742cfa2..3e5074342 100644 --- a/test/centrality/pagerank.jl +++ b/test/centrality/pagerank.jl @@ -1,5 +1,9 @@ @testset "Pagerank" begin - @test_approx_eq_eps(pagerank(g5)[3], 0.318, 0.001) - @test_throws ErrorException pagerank(g5, 2) - @test_throws ErrorException pagerank(g5, 0.85, 2) + g5 = DiGraph(4) + add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) + for g in testdigraphs(g5) + @test @inferred(pagerank(g))[3] ≈ 0.318 atol=0.001 + @test_throws ErrorException pagerank(g, 2) + @test_throws ErrorException pagerank(g, 0.85, 2) + end end diff --git a/test/cliques.jl b/test/community/cliques.jl similarity index 64% rename from test/cliques.jl rename to test/community/cliques.jl index 95c9f6d1e..310492756 100644 --- a/test/cliques.jl +++ b/test/community/cliques.jl @@ -12,15 +12,18 @@ function test_cliques(graph, expected) # Make test results insensitive to ordering - setofsets(maximal_cliques(graph)) == setofsets(expected) + setofsets(@inferred(maximal_cliques(graph))) == setofsets(expected) end - g = Graph(3) - add_edge!(g, 1, 2) - @test test_cliques(g, Array[[1,2], [3]]) - add_edge!(g, 2, 3) - @test test_cliques(g, Array[[1,2], [2,3]]) - + gx = Graph(3) + add_edge!(gx, 1, 2) + for g in testgraphs(gx) + @test test_cliques(g, Array[[1,2], [3]]) + end + add_edge!(gx, 2, 3) + for g in testgraphs(gx) + @test test_cliques(g, Array[[1,2], [2,3]]) + end # Test for "pivotdonenbrs not defined" bug h = Graph(6) add_edge!(h, 1, 2) @@ -32,7 +35,9 @@ add_edge!(h, 3, 6) add_edge!(h, 5, 6) - @test maximal_cliques(h) != [] + for g in testgraphs(h) + @test !isempty(@inferred(maximal_cliques(g))) + end # test for extra cliques bug @@ -44,5 +49,7 @@ add_edge!(h,4,5) add_edge!(h,4,7) add_edge!(h,5,7) - @test test_cliques(h, Array[[7,4,5], [2,6], [3,5], [3,6], [3,1]]) + for g in testgraphs(h) + @test test_cliques(h, Array[[7,4,5], [2,6], [3,5], [3,6], [3,1]]) + end end diff --git a/test/community/clustering.jl b/test/community/clustering.jl index 8fac09c8e..95df102ce 100644 --- a/test/community/clustering.jl +++ b/test/community/clustering.jl @@ -1,8 +1,10 @@ @testset "Clustering" begin g10 = CompleteGraph(10) - @test local_clustering_coefficient(g10) == ones(10) - @test global_clustering_coefficient(g10) == 1 - @test local_clustering(g10) == (fill(36, 10), fill(36, 10)) - @test triangles(g10) == fill(36, 10) - @test triangles(g10, 1) == 36 + for g in testgraphs(g10) + @test @inferred(local_clustering_coefficient(g)) == ones(10) + @test @inferred(global_clustering_coefficient(g)) == 1 + @test @inferred(local_clustering(g)) == (fill(36, 10), fill(36, 10)) + @test @inferred(triangles(g)) == fill(36, 10) + @test @inferred(triangles(g, 1)) == 36 + end end diff --git a/test/community/core-periphery.jl b/test/community/core-periphery.jl index 8f09d7b48..8dc96fcfe 100644 --- a/test/community/core-periphery.jl +++ b/test/community/core-periphery.jl @@ -1,22 +1,26 @@ @testset "Core periphery" begin g10 = StarGraph(10) - c = core_periphery_deg(g10) - @test degree(g10,1) == 9 - @test c[1] == 1 - for i=2:10 - @test c[i] == 2 + for g in testgraphs(g10) + c = core_periphery_deg(g) + @test @inferred(degree(g, 1)) == 9 + @test c[1] == 1 + for i=2:10 + @test c[i] == 2 + end end g10 = StarGraph(10) g10 = blkdiag(g10,g10) add_edge!(g10, 1, 11) - c = core_periphery_deg(g10) - @test c[1] == 1 - @test c[11] == 1 - for i=2:10 - @test c[i] == 2 - end - for i=12:20 - @test c[i] == 2 + for g in testgraphs(g10) + c = @inferred(core_periphery_deg(g)) + @test c[1] == 1 + @test c[11] == 1 + for i=2:10 + @test c[i] == 2 + end + for i=12:20 + @test c[i] == 2 + end end end diff --git a/test/community/label_propagation.jl b/test/community/label_propagation.jl index 368a68836..3f6152611 100644 --- a/test/community/label_propagation.jl +++ b/test/community/label_propagation.jl @@ -1,16 +1,18 @@ @testset "Label propagation" begin n=10 g10 = CompleteGraph(n) - z = copy(g10) - for k=2:5 - z = blkdiag(z, g10) - add_edge!(z, (k-1)*n, k*n) - c, ch = label_propagation(z) - a = collect(n:n:k*n) - a = Int[div(i-1,n)+1 for i=1:k*n] - # check the number of community - @test length(unique(a)) == length(unique(c)) - # check the partition - @test a == c + for g in testgraphs(g10) + z = copy(g) + for k=2:5 + z = blkdiag(z, g) + add_edge!(z, (k-1)*n, k*n) + c, ch = @inferred(label_propagation(z)) + a = collect(n:n:k*n) + a = Int[div(i-1,n)+1 for i=1:k*n] + # check the number of communities + @test length(unique(a)) == length(unique(c)) + # check the partition + @test a == c + end end end diff --git a/test/community/modularity.jl b/test/community/modularity.jl index dc9581969..98fe437d6 100644 --- a/test/community/modularity.jl +++ b/test/community/modularity.jl @@ -2,9 +2,13 @@ n = 10 m = n*(n-1)/2 c = ones(Int, n) - g = CompleteGraph(n) - @test modularity(g, c) == 0 - # - g = Graph(n) - @test modularity(g, c) == 0 + gint = CompleteGraph(n) + for g in testgraphs(gint) + @test @inferred(modularity(g, c)) == 0 + end + + gint = Graph(n) + for g in testgraphs(gint) + @test @inferred(modularity(g, c)) == 0 + end end diff --git a/test/connectivity.jl b/test/connectivity.jl index 8d0e01911..d286e00f4 100644 --- a/test/connectivity.jl +++ b/test/connectivity.jl @@ -1,44 +1,55 @@ @testset "Connectivity" begin - g = PathGraph(4) - add_vertices!(g,10) - add_edge!(g,5,6) - add_edge!(g,6,7) - add_edge!(g,8,9) - add_edge!(g,10,9) - - - @test !is_connected(g) - @test is_connected(g6) - - cc = connected_components(g) - label = zeros(Int, nv(g)) - LightGraphs.connected_components!(label, g) - @test label[1:10] == [1,1,1,1,5,5,5,8,8,8] - import LightGraphs: components, components_dict - cclab = components_dict(label) - @test cclab[1] == [1,2,3,4] - @test cclab[5] == [5,6,7] - @test cclab[8] == [8,9,10] - @test length(cc) >= 3 && sort(cc[3]) == [8,9,10] - - g10 =DiGraph(4) + g6 = smallgraph(:house) + gx = PathGraph(4) + add_vertices!(gx,10) + add_edge!(gx,5,6) + add_edge!(gx,6,7) + add_edge!(gx,8,9) + add_edge!(gx,10,9) + + + for g in testgraphs(gx) + @test @inferred(!is_connected(g)) + cc = @inferred(connected_components(g)) + label = zeros(eltype(g), nv(g)) + @inferred(LightGraphs.connected_components!(label, g)) + @test label[1:10] == [1,1,1,1,5,5,5,8,8,8] + import LightGraphs: components, components_dict + cclab = @inferred(components_dict(label)) + @test cclab[1] == [1,2,3,4] + @test cclab[5] == [5,6,7] + @test cclab[8] == [8,9,10] + @test length(cc) >= 3 && sort(cc[3]) == [8,9,10] + end + for g in testgraphs(g6) + @test @inferred(is_connected(g)) + end + + + g10 = DiGraph(4) add_edge!(g10,1,3) add_edge!(g10,2,4) - @test is_bipartite(g10) == true + for g in testdigraphs(g10) + @test @inferred(is_bipartite(g)) + end add_edge!(g10,1,4) - @test is_bipartite(g10) == true + for g in testdigraphs(g10) + @test @inferred(is_bipartite(g)) + end g10 = DiGraph(20) - for m=1:50 + for g in testdigraphs(g10) + for m=1:50 i = rand(1:10) j = rand(11:20) if rand() < 0.5 i, j = j, i end - if !has_edge(g10, i, j) - add_edge!(g10, i, j) - @test is_bipartite(g10) == true + if !has_edge(g, i, j) + add_edge!(g, i, j) + @test @inferred(is_bipartite(g)) end + end end # graph from https://en.wikipedia.org/wiki/Strongly_connected_component @@ -48,41 +59,49 @@ add_edge!(h,4,3); add_edge!(h,4,8); add_edge!(h,5,1); add_edge!(h,5,6); add_edge!(h,6,7); add_edge!(h,7,6); add_edge!(h,8,4); add_edge!(h,8,7) + for g in testdigraphs(h) + @test @inferred(is_connected(g)) + scc = @inferred(strongly_connected_components(g)) + wcc = @inferred(weakly_connected_components(g)) - @test is_connected(h) + @test length(scc) == 3 && sort(scc[3]) == [1,2,5] + @test length(wcc) == 1 && length(wcc[1]) == nv(g) - scc = strongly_connected_components(h) - wcc = weakly_connected_components(h) + end - @test length(scc) == 3 && sort(scc[3]) == [1,2,5] - @test length(wcc) == 1 && length(wcc[1]) == nv(h) function scc_ok(graph) """Check that all SCC really are strongly connected""" - scc = strongly_connected_components(graph) + scc = @inferred(strongly_connected_components(graph)) scc_as_subgraphs = map(i -> graph[i], scc) return all(is_strongly_connected, scc_as_subgraphs) end # the two graphs below are isomorphic (exchange 2 <--> 4) h = DiGraph(4); add_edge!(h, 1, 4); add_edge!(h, 4, 2); add_edge!(h, 2, 3); add_edge!(h, 1, 3); + for g in testdigraphs(h) + @test scc_ok(g) + end + h2 = DiGraph(4); add_edge!(h2, 1, 2); add_edge!(h2, 2, 4); add_edge!(h2, 4, 3); add_edge!(h2, 1, 3); - @test scc_ok(h) - @test scc_ok(h2) + for g in testdigraphs(h2) + @test scc_ok(g) + end h = DiGraph(6) add_edge!(h,1,3); add_edge!(h,3,4); add_edge!(h,4,2); add_edge!(h,2,1) add_edge!(h,3,5); add_edge!(h,5,6); add_edge!(h,6,4) - - scc = strongly_connected_components(h) - - @test length(scc) == 1 && sort(scc[1]) == [1:6;] - + for g in testdigraphs(h) + scc = @inferred(strongly_connected_components(g)) + @test length(scc) == 1 && sort(scc[1]) == [1:6;] + end # tests from Graphs.jl h = DiGraph(4) add_edge!(h,1,2); add_edge!(h,2,3); add_edge!(h,3,1); add_edge!(h,4,1) - scc = strongly_connected_components(h) - @test length(scc) == 2 && sort(scc[1]) == [1:3;] && sort(scc[2]) == [4] + for g in testdigraphs(h) + scc = @inferred(strongly_connected_components(g)) + @test length(scc) == 2 && sort(scc[1]) == [1:3;] && sort(scc[2]) == [4] + end h = DiGraph(12) add_edge!(h,1,2); add_edge!(h,2,3); add_edge!(h,2,4); add_edge!(h,2,5); @@ -91,13 +110,14 @@ add_edge!(h,7,8); add_edge!(h,7,10); add_edge!(h,8,7); add_edge!(h,9,7); add_edge!(h,10,9); add_edge!(h,10,11); add_edge!(h,11,12); add_edge!(h,12,10) - scc = strongly_connected_components(h) - @test length(scc) == 4 - @test sort(scc[1]) == [7,8,9,10,11,12] - @test sort(scc[2]) == [3, 6] - @test sort(scc[3]) == [2, 4, 5] - @test scc[4] == [1] - + for g in testdigraphs(h) + scc = @inferred(strongly_connected_components(g)) + @test length(scc) == 4 + @test sort(scc[1]) == [7,8,9,10,11,12] + @test sort(scc[2]) == [3, 6] + @test sort(scc[3]) == [2, 4, 5] + @test scc[4] == [1] + end # Test examples with self-loops from # Graph-Theoretic Analysis of Finite Markov Chains by J.P. Jarvis & D. R. Shier @@ -135,37 +155,39 @@ fig8[[2,10,13,21,24,27,35]] = 1 fig8 = DiGraph(fig8) - @test Set(strongly_connected_components(fig1)) == Set(scc_fig1) - @test Set(strongly_connected_components(fig3)) == Set(scc_fig3) + @test Set(@inferred(strongly_connected_components(fig1))) == Set(scc_fig1) + @test Set(@inferred(strongly_connected_components(fig3))) == Set(scc_fig3) - @test period(n_ring) == n - @test period(n_ring_shortcut) == 2 + @test @inferred(period(n_ring)) == n + @test @inferred(period(n_ring_shortcut)) == 2 - @test condensation(fig3) == fig3_cond + @test @inferred(condensation(fig3)) == fig3_cond - @test attracting_components(fig1) == Vector[[2,5]] - @test attracting_components(fig3) == Vector[[3,4],[8]] + @test @inferred(attracting_components(fig1)) == Vector[[2,5]] + @test @inferred(attracting_components(fig3)) == Vector[[3,4],[8]] g10 = StarGraph(10) - @test neighborhood(g10, 1 , 0) == [1] - @test length(neighborhood(g10, 1, 1)) == 10 - @test length(neighborhood(g10, 2, 1)) == 2 - @test length(neighborhood(g10, 1, 2)) == 10 - @test length(neighborhood(g10, 2, 2)) == 10 - + for g in testgraphs(g10) + @test @inferred(neighborhood(g, 1 , 0)) == [1] + @test length(@inferred(neighborhood(g, 1, 1))) == 10 + @test length(@inferred(neighborhood(g, 2, 1))) == 2 + @test length(@inferred(neighborhood(g, 1, 2))) == 10 + @test length(@inferred(neighborhood(g, 2, 2))) == 10 + end g10 = StarDiGraph(10) - @test neighborhood(g10, 1 , 0, dir=:out) == [1] - @test length(neighborhood(g10, 1, 1, dir=:out)) == 10 - @test length(neighborhood(g10, 2, 1, dir=:out)) == 1 - @test length(neighborhood(g10, 1, 2, dir=:out)) == 10 - @test length(neighborhood(g10, 2, 2, dir=:out)) == 1 - @test neighborhood(g10, 1 , 0, dir=:in) == [1] - @test length(neighborhood(g10, 1, 1, dir=:in)) == 1 - @test length(neighborhood(g10, 2, 1, dir=:in)) == 2 - @test length(neighborhood(g10, 1, 2, dir=:in)) == 1 - @test length(neighborhood(g10, 2, 2, dir=:in)) == 2 - - @test !isgraphical([1,1,1]) - @test isgraphical([2,2,2]) - @test isgraphical(fill(3,10)) + for g in testdigraphs(g10) + @test @inferred(neighborhood(g10, 1 , 0, dir=:out)) == [1] + @test length(@inferred(neighborhood(g, 1, 1, dir=:out))) == 10 + @test length(@inferred(neighborhood(g, 2, 1, dir=:out))) == 1 + @test length(@inferred(neighborhood(g, 1, 2, dir=:out))) == 10 + @test length(@inferred(neighborhood(g, 2, 2, dir=:out))) == 1 + @test @inferred(neighborhood(g, 1 , 0, dir=:in)) == [1] + @test length(@inferred(neighborhood(g, 1, 1, dir=:in))) == 1 + @test length(@inferred(neighborhood(g, 2, 1, dir=:in))) == 2 + @test length(@inferred(neighborhood(g, 1, 2, dir=:in))) == 1 + @test length(@inferred(neighborhood(g, 2, 2, dir=:in))) == 2 + end + @test @inferred(!isgraphical([1,1,1])) + @test @inferred(isgraphical([2,2,2])) + @test @inferred(isgraphical(fill(3,10))) end diff --git a/test/core.jl b/test/core.jl index 996578164..241f84767 100644 --- a/test/core.jl +++ b/test/core.jl @@ -1,174 +1,75 @@ @testset "Core" begin - @test e1.src == src(e1) == 1 - @test e1.dst == dst(e1) == 2 - @test reverse(e1) == re1 - @test sprint(show, e1) == "Edge 1 => 2" - @test Pair(e1) == Pair(1,2) - @test Tuple(e1) == (1,2) - - e2 = Edge(1,3) - e3 = Edge(1,4) - e4 = Edge(2,5) - e5 = Edge(3,5) - - @test LightGraphs.is_ordered(e5) - @test !LightGraphs.is_ordered(reverse(e5)) - - g = Graph(5) - @test add_edge!(g, 1, 2) - @test add_edge!(g, e2) - @test add_edge!(g, e3) - @test add_edge!(g, e4) - @test add_edge!(g, e5) - - - h = DiGraph(5) - @test add_edge!(h, 1, 2) - @test add_edge!(h, e2) - @test add_edge!(h, e3) - @test add_edge!(h, e4) - @test add_edge!(h, e5) - - @test vertices(g) == 1:5 - i = 0 - for e in edges(g) - i+=1 +e2 = Edge(1,3) +e3 = Edge(1,4) +@test @inferred(is_ordered(e2)) +@test @inferred(!is_ordered(reverse(e3))) + +gx = Graph(10) +for g in testgraphs(gx) + add_vertices!(g, 5) + @test @inferred(nv(g)) == 15 +end + +g5w = WheelGraph(5) +for g in testgraphs(g5w) + @test @inferred(indegree(g,1)) == @inferred(outdegree(g,1)) == @inferred(degree(g,1)) == 4 + @test @inferred(indegree(g)) == @inferred(outdegree(g)) == @inferred(degree(g)) == [4,3,3,3,3] + + @test @inferred(Δout(g)) == @inferred(Δin(g)) == @inferred(Δ(g)) == 4 + @test @inferred(δout(g)) == @inferred(δin(g)) == @inferred(δ(g)) == 3 + + z = @inferred(degree_histogram(g)) + @test z.weights == [4,0,1] + + @test @inferred(neighbors(g, 2)) == @inferred(all_neighbors(g, 2)) == [1,3,5] + @test @inferred(common_neighbors(g, 1, 5)) == [2, 4] + + gsl = copy(g) + add_edge!(gsl, 3, 3) + add_edge!(gsl, 2, 2) + + @test @inferred(!has_self_loops(g)) + @test @inferred(has_self_loops(gsl)) + @test @inferred(num_self_loops(g)) == 0 + @test @inferred(num_self_loops(gsl)) == 2 + + @test @inferred(density(g)) == 0.8 + + @test eltype(squash(g)) == UInt8 end - @test i == 5 - @test has_edge(g,3,5) - # @test edges(g) == Set([e1, e2, e3, e4, e5]) - # @test Set{Edge}(edges(g)) == Set([e1, e2, e3, e4, e5]) - # fadj, badj, and adj tested in graphdigraph.jl - - @test degree(g) == [3, 2, 2, 1, 2] - @test indegree(g) == [3, 2, 2, 1, 2] - @test indegree(g,1) == 3 - @test outdegree(g) == [3, 2, 2, 1, 2] - @test outdegree(g,1) == 3 - @test degree(h) == [3, 2, 2, 1, 2] - @test indegree(h) == [0, 1, 1, 1, 2] - @test indegree(h,1) == 0 - @test outdegree(h) == [3, 1, 1, 0, 0] - @test outdegree(h,1) == 3 - @test in_neighbors(h,5) == LightGraphs.badj(h)[5] == LightGraphs.badj(h,5) == [2, 3] - @test out_neighbors(h,1) == LightGraphs.fadj(h)[1] == LightGraphs.fadj(h,1) == [2, 3, 4] - - @test p1 == g2 - @test issubset(h2, h1) - - @test has_edge(g, 1, 2) - @test in_edges(g, 2) == [e1, reverse(e4)] - @test out_edges(g, 1) == [e1, e2, e3] - - @test add_vertex!(g) && nv(g) == 6 - @test add_vertices!(g,5) && nv(g) == 11 - @test has_vertex(g, 11) - @test ne(g) == 5 - @test !is_directed(g) - @test is_directed(h) - - @test δ(g) == δin(g) == δout(g) == 0 - @test Δ(g) == Δout(g) == 3 - @test Δin(h) == 2 - @test δ(h) == 1 - @test δin(h) == 0 - @test δout(h) == 0 - @test CompleteGraph(4) == CompleteGraph(4) - @test CompleteGraph(4) != PathGraph(4) - @test CompleteDiGraph(4) != PathDiGraph(4) - @test CompleteDiGraph(4) == CompleteDiGraph(4) - - @test degree_histogram(CompleteDiGraph(10)).weights == [10] - @test degree_histogram(CompleteGraph(10)).weights == [10] - - @test neighbors(g, 1) == [2, 3, 4] - @test common_neighbors(g, 2, 3) == [1, 5] - @test common_neighbors(h, 2, 3) == [5] - - @test add_edge!(g, 1, 1) - @test has_self_loops(g) - @test num_self_loops(g) == 1 - @test !add_edge!(g, 1, 1) - @test rem_edge!(g, 1, 1) - @test !rem_edge!(g, 1, 1) - @test ne(g) == 5 - @test rem_edge!(g, 1, 2) - @test ne(g) == 4 - @test !rem_edge!(g, 2, 1) - add_edge!(g, 1, 2) - @test ne(g) == 5 - - @test has_edge(g,2,1) - @test has_edge(g,1,2) - @test rem_edge!(g, 2, 1) - @test add_edge!(h, 1, 1) - @test rem_edge!(h, 1, 1) - @test rem_edge!(h, 1, 2) - @test !rem_edge!(h, 1, 2) - - function test_rem_edge(g, srcv) - srcv = 2 - for dstv in collect(neighbors(g, srcv)) - rem_edge!(g, srcv, dstv) + + g5wd = WheelDiGraph(5) + for g in testdigraphs(g5wd) + @test @inferred(indegree(g,2)) == 2 + @test @inferred(outdegree(g,2)) == 1 + @test @inferred(degree(g,2)) == 3 + @test @inferred(indegree(g)) == [0,2,2,2,2] + @test @inferred(outdegree(g)) == [4,1,1,1,1] + @test @inferred(degree(g)) == [4,3,3,3,3] + + @test @inferred(Δout(g)) == @inferred(Δ(g)) == 4 + @test @inferred(Δin(g)) == 2 + @test @inferred(δout(g)) == 1 + @test @inferred(δin(g)) == 0 + @test @inferred(δ(g)) == 3 + + z = @inferred(degree_histogram(g)) + @test z.weights == [4,0,1] + + @test @inferred(neighbors(g, 2)) == [3] + @test Set(@inferred(all_neighbors(g, 2))) == Set([1,3,5]) + @test @inferred(common_neighbors(g, 1, 5)) == [2] + + gsl = copy(g) + add_edge!(gsl, 3, 3) + add_edge!(gsl, 2, 2) + + @test @inferred(!has_self_loops(g)) + @test @inferred(has_self_loops(gsl)) + @test @inferred(num_self_loops(g)) == 0 + @test @inferred(num_self_loops(gsl)) == 2 + + @test @inferred(density(g)) == 0.4 + @test eltype(squash(g)) == UInt8 end - @test length(neighbors(g,srcv)) == 0 - end - for v in vertices(g) - test_rem_edge(copy(g),v) end - for v in vertices(h) - test_rem_edge(copy(h),v) - end - - @test g == copy(g) - @test !(g === copy(g)) - g10 = CompleteGraph(5) - @test rem_vertex!(g10, 1) - @test g10 == CompleteGraph(4) - @test rem_vertex!(g10, 4) - @test g10 == CompleteGraph(3) - @test !rem_vertex!(g10, 9) - - g10 = CompleteDiGraph(5) - @test rem_vertex!(g10, 1) - @test g10 == CompleteDiGraph(4) - rem_vertex!(g10, 4) - @test g10 == CompleteDiGraph(3) - @test !rem_vertex!(g10, 9) - g10 = PathGraph(5) - @test rem_vertex!(g10, 5) - @test g10 == PathGraph(4) - @test rem_vertex!(g10, 4) - @test g10 == PathGraph(3) - - g10 = PathDiGraph(5) - @test rem_vertex!(g10, 5) - @test g10 == PathDiGraph(4) - @test rem_vertex!(g10, 4) - @test g10 == PathDiGraph(3) - - g10 = PathDiGraph(5) - @test rem_vertex!(g10, 1) - h10 = PathDiGraph(6) - @test rem_vertex!(h10, 1) - @test rem_vertex!(h10, 1) - @test g10 == h10 - - g10 = CycleGraph(5) - @test rem_vertex!(g10, 5) - @test g10 == PathGraph(4) - - g10 = PathGraph(3) - @test rem_vertex!(g10, 2) - @test g10 == Graph(2) - - g10 = PathGraph(4) - @test rem_vertex!(g10, 3) - h10 =Graph(3) - @test add_edge!(h10,1,2) - @test g10 == h10 - - g10 = CompleteGraph(5) - @test rem_vertex!(g10, 3) - @test g10 == CompleteGraph(4) -end diff --git a/test/distance.jl b/test/distance.jl index a0b4a7c8e..60de14450 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -1,21 +1,34 @@ @testset "Distance" begin - @test_throws ErrorException eccentricity(g4) - z = eccentricity(a1, distmx1) - @test z == [6.2, 4.2, 6.2] - @test diameter(z) == diameter(a1, distmx1) == 6.2 - @test periphery(z) == periphery(a1, distmx1) == [1,3] - @test radius(z) == radius(a1, distmx1) == 4.2 - @test center(z) == center(a1, distmx1) == [2] + g4 = PathDiGraph(5) + adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph + adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph + a1 = Graph(adjmx1) + a2 = DiGraph(adjmx2) + distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] + distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] - z = eccentricity(a2, distmx2) - @test z == [6.2, 4.2, 6.1] - @test diameter(z) == diameter(a2, distmx2) == 6.2 - @test periphery(z) == periphery(a2, distmx2) == [1] - @test radius(z) == radius(a2, distmx2) == 4.2 - @test center(z) == center(a2, distmx2) == [2] + for g in testdigraphs(g4) + @test_throws ErrorException eccentricity(g) + end + for g in testgraphs(a1) + z = @inferred(eccentricity(g, distmx1)) + @test z == [6.2, 4.2, 6.2] + @test @inferred(diameter(z)) == diameter(g, distmx1) == 6.2 + @test @inferred(periphery(z)) == periphery(g, distmx1) == [1,3] + @test @inferred(radius(z)) == radius(g, distmx1) == 4.2 + @test @inferred(center(z)) == center(g, distmx1) == [2] + end + for g in testdigraphs(a2) + z = @inferred(eccentricity(g, distmx2)) + @test z == [6.2, 4.2, 6.1] + @test @inferred(diameter(z)) == diameter(g, distmx2) == 6.2 + @test @inferred(periphery(z)) == periphery(g, distmx2) == [1] + @test @inferred(radius(z)) == radius(g, distmx2) == 4.2 + @test @inferred(center(z)) == center(g, distmx2) == [2] + end @test size(LightGraphs.DefaultDistance()) == (typemax(Int), typemax(Int)) - d = LightGraphs.DefaultDistance(3) + d = @inferred(LightGraphs.DefaultDistance(3)) @test size(d) == (3, 3) @test d[1,1] == getindex(d, 1, 1) == 1 @test d[1:2, 1:2] == LightGraphs.DefaultDistance(2) diff --git a/test/edgeiter.jl b/test/edgeiter.jl deleted file mode 100644 index 57dbfd3df..000000000 --- a/test/edgeiter.jl +++ /dev/null @@ -1,52 +0,0 @@ -@testset "Edgeiter" begin - ga = Graph(10,20; seed=1) - gb = Graph(10,20; seed=1) - @test sprint(show,edges(ga)) == "EdgeIter 20" - @test sprint(show, start(edges(ga))) == "EdgeIterState [1, 1, false]" - - @test length(collect(edges(Graph(0,0)))) == 0 - - @test edges(ga) == edges(gb) - @test edges(ga) == collect(Edge, edges(gb)) - @test collect(Edge, edges(gb)) == edges(ga) - @test Set{Edge}(collect(Edge, edges(gb))) == edges(ga) - @test edges(ga) == Set{Edge}(collect(Edge, edges(gb))) - - @test eltype(edges(ga)) == Edge - - ga = DiGraph(10,20; seed=1) - gb = DiGraph(10,20; seed=1) - @test edges(ga) == edges(gb) - @test edges(ga) == collect(Edge, edges(gb)) - @test collect(Edge, edges(gb)) == edges(ga) - @test Set{Edge}(collect(Edge, edges(gb))) == edges(ga) - @test edges(ga) == Set{Edge}(collect(Edge, edges(gb))) - - ga = Graph(10) - add_edge!(ga, 3, 2) - add_edge!(ga, 3, 10) - add_edge!(ga, 5, 10) - add_edge!(ga, 10, 3) - - eit = edges(ga) - es = start(eit) - - @test es.s == 2 - @test es.di == 1 - - @test [e for e in eit] == [Edge(2, 3), Edge(3, 10), Edge(5,10)] - - ga = DiGraph(10) - add_edge!(ga, 3, 2) - add_edge!(ga, 3, 10) - add_edge!(ga, 5, 10) - add_edge!(ga, 10, 3) - - eit = edges(ga) - es = start(eit) - - @test es.s == 3 - @test es.di == 1 - - @test [e for e in eit] == [Edge(3, 2), Edge(3, 10), Edge(5,10), Edge(10,3)] -end diff --git a/test/edit_distance.jl b/test/edit_distance.jl index 79f1526d8..e8fe182ea 100644 --- a/test/edit_distance.jl +++ b/test/edit_distance.jl @@ -1,24 +1,39 @@ @testset "Edit distance" begin - d, λ = edit_distance(triangle, quadrangle, subst_cost=MinkowskiCost(1:3,1:4)) - @test d == 1.0 - @test λ == Tuple[(1,1),(2,2),(3,3),(0,4)] - d, λ = edit_distance(quadrangle, triangle, subst_cost=MinkowskiCost(1:4,1:3)) - @test d == 1.0 - @test λ == Tuple[(1,1),(2,2),(3,3),(4,0)] + gtri = random_regular_graph(3,2) + gquad = random_regular_graph(4,2) + gpent = random_regular_graph(5,2) - d, λ = edit_distance(triangle, pentagon, subst_cost=MinkowskiCost(1:3,1:5)) - @test d == 2.0 - @test λ == Tuple[(1,1),(2,2),(3,3),(0,4),(0,5)] + for triangle in testgraphs(gtri), quadrangle in testgraphs(gquad), pentagon in testgraphs(gpent) + d, λ = @inferred(edit_distance(triangle, quadrangle, subst_cost=MinkowskiCost(1:3,1:4))) + @test d == 1.0 + @test λ == Tuple[(1,1),(2,2),(3,3),(0,4)] - d, λ = edit_distance(pentagon, triangle, subst_cost=MinkowskiCost(1:5,1:3)) - @test d == 2.0 - @test λ == Tuple[(1,1),(2,2),(3,3),(4,0),(5,0)] + d, λ = @inferred(edit_distance(quadrangle, triangle, subst_cost=MinkowskiCost(1:4,1:3))) + @test d == 1.0 + @test λ == Tuple[(1,1),(2,2),(3,3),(4,0)] - cost = MinkowskiCost(1:3,1:3) - bcost = BoundedMinkowskiCost(1:3,1:3) + d, λ = @inferred(edit_distance(triangle, pentagon, subst_cost=MinkowskiCost(1:3,1:5))) + @test d == 2.0 + @test λ == Tuple[(1,1),(2,2),(3,3),(0,4),(0,5)] + + d, λ = @inferred(edit_distance(pentagon, triangle, subst_cost=MinkowskiCost(1:5,1:3))) + @test d == 2.0 + @test λ == Tuple[(1,1),(2,2),(3,3),(4,0),(5,0)] + end + cost = @inferred(MinkowskiCost(1:3,1:3)) + bcost = @inferred(BoundedMinkowskiCost(1:3,1:3)) for i=1:3 @test cost(i,i) == 0. @test bcost(i,i) == 2/3 end + + g1c = CompleteGraph(4) + g2c = CompleteGraph(4) + rem_edge!(g2c, 1, 2) + for g1 in testgraphs(g1c), g2 in testgraphs(g2c) + d, λ = @inferred(edit_distance(g1, g2)) + @test d == 2.0 + @test λ == Tuple[(1,1),(2,2),(3,3),(4,4)] + end end diff --git a/test/flow/boykov_kolmogorov.jl b/test/flow/boykov_kolmogorov.jl index 44fd8da5d..03bfd6403 100644 --- a/test/flow/boykov_kolmogorov.jl +++ b/test/flow/boykov_kolmogorov.jl @@ -1,29 +1,30 @@ @testset "Boykov Kolmogorov" begin # construct graph - G = DiGraph(3) - add_edge!(G,1,2) - add_edge!(G,2,3) + gg = DiGraph(3) + add_edge!(gg,1,2) + add_edge!(gg,2,3) # source and sink terminals source, target = 1, 3 - # default capacity - capacity_matrix = LightGraphs.DefaultCapacity(G) - - # state variables - flow_matrix = zeros(Int, 3, 3) - - TREE = zeros(Int, 3) - TREE[source] = 1 - TREE[target] = 2 - - PARENT = zeros(Int, 3) - A = [source,target] - - residual_graph = LightGraphs.residual(G) - - path = LightGraphs.find_path!(residual_graph, source, target, flow_matrix, capacity_matrix, PARENT, TREE, A) - - @test path == [1,2,3] + for g in testdigraphs(gg) + # default capacity + capacity_matrix = LightGraphs.DefaultCapacity(g) + residual_graph = @inferred(LightGraphs.residual(g)) + T = eltype(g) + flow_matrix = zeros(T, 3, 3) + TREE = zeros(T, 3) + TREE[source] = T(1) + TREE[target] = T(2) + PARENT = zeros(T, 3) + A = [T(source),T(target)] +# see https://github.com/JuliaLang/julia/issues/21077 +# @show("testing $g with eltype $T, residual_graph type is $(eltype(residual_graph)), flow_matrix type is $(eltype(flow_matrix)), capacity_matrix type is $(eltype(capacity_matrix))") + path = LightGraphs.find_path!( + residual_graph, source, target, flow_matrix, + capacity_matrix, PARENT, TREE, A) + + @test path == [1,2,3] + end end diff --git a/test/flow/dinic.jl b/test/flow/dinic.jl index 6629e33e7..1165c5769 100644 --- a/test/flow/dinic.jl +++ b/test/flow/dinic.jl @@ -18,43 +18,45 @@ end # Construct the residual graph - residual_graph = LightGraphs.residual(flow_graph) - - # Test with default distances - @test LightGraphs.dinic_impl(residual_graph, 1, 8, LightGraphs.DefaultCapacity(residual_graph))[1] == 3 - - # Test with capacity matrix - @test LightGraphs.dinic_impl(residual_graph, 1, 8, capacity_matrix)[1] == 28 - - # Test on disconnected graphs - function test_blocking_flow(residual_graph, source, target, capacity_matrix, flow_matrix) - #disconnect source - h = copy(residual_graph) - for dst in collect(neighbors(residual_graph, source)) - rem_edge!(h, source, dst) - end - @test LightGraphs.blocking_flow!(h, source, target, capacity_matrix, flow_matrix) == 0 - - #disconnect target and add unreachable vertex - h = copy(residual_graph) - for src in collect(in_neighbors(residual_graph, target)) - rem_edge!(h, src, target) - end - @test LightGraphs.blocking_flow!(h, source, target, capacity_matrix, flow_matrix) == 0 - - # unreachable vertex (covers the case where a vertex isn't reachable from the source) - h = copy(residual_graph) - add_vertex!(h) - add_edge!(h, nv(residual_graph)+1, target) - capacity_matrix_ = vcat(hcat(capacity_matrix, zeros(Int, nv(residual_graph))), zeros(Int, 1, nv(residual_graph)+1)) - flow_graph_ = vcat(hcat(flow_matrix, zeros(Int, nv(residual_graph))), zeros(Int, 1, nv(residual_graph)+1)) - - @test LightGraphs.blocking_flow!(h, source, target, capacity_matrix_, flow_graph_ ) > 0 - - #test with connected graph - @test LightGraphs.blocking_flow!(residual_graph, source, target, capacity_matrix, flow_matrix) > 0 + for fg in (flow_graph, DiGraph{UInt8}(flow_graph), DiGraph{Int16}(flow_graph)) + residual_graph = @inferred(LightGraphs.residual(fg)) + + # Test with default distances + @test @inferred(LightGraphs.dinic_impl(residual_graph, 1, 8, LightGraphs.DefaultCapacity(residual_graph)))[1] == 3 + + # Test with capacity matrix + @test @inferred(LightGraphs.dinic_impl(residual_graph, 1, 8, capacity_matrix))[1] == 28 + + # Test on disconnected graphs + function test_blocking_flow(residual_graph, source, target, capacity_matrix, flow_matrix) + #disconnect source + h = copy(residual_graph) + for dst in collect(neighbors(residual_graph, source)) + rem_edge!(h, source, dst) + end + @test @inferred(LightGraphs.blocking_flow(h, source, target, capacity_matrix, flow_matrix)) == 0 + + #disconnect target and add unreachable vertex + h = copy(residual_graph) + for src in collect(in_neighbors(residual_graph, target)) + rem_edge!(h, src, target) + end + @test @inferred(LightGraphs.blocking_flow(h, source, target, capacity_matrix, flow_matrix)) == 0 + + # unreachable vertex (covers the case where a vertex isn't reachable from the source) + h = copy(residual_graph) + add_vertex!(h) + add_edge!(h, nv(residual_graph)+1, target) + capacity_matrix_ = vcat(hcat(capacity_matrix, zeros(Int, nv(residual_graph))), zeros(Int, 1, nv(residual_graph)+1)) + flow_graph_ = vcat(hcat(flow_matrix, zeros(Int, nv(residual_graph))), zeros(Int, 1, nv(residual_graph)+1)) + + @test @inferred(LightGraphs.blocking_flow(h, source, target, capacity_matrix_, flow_graph_ )) > 0 + + #test with connected graph + @test @inferred(LightGraphs.blocking_flow(residual_graph, source, target, capacity_matrix, flow_matrix)) > 0 + end + + flow_matrix = zeros(Int, nv(residual_graph), nv(residual_graph)) + test_blocking_flow(residual_graph, 1, 8, capacity_matrix, flow_matrix) end - - flow_matrix = zeros(Int, nv(residual_graph), nv(residual_graph)) - test_blocking_flow(residual_graph, 1, 8, capacity_matrix, flow_matrix) end diff --git a/test/flow/edmonds_karp.jl b/test/flow/edmonds_karp.jl index 6f97a86e9..5cacd1b5c 100644 --- a/test/flow/edmonds_karp.jl +++ b/test/flow/edmonds_karp.jl @@ -9,52 +9,55 @@ (5,8,10),(6,7,15),(6,8,10),(7,3,6),(7,8,10) ] - capacity_matrix = zeros(Int,8,8) + for fg in (flow_graph, DiGraph{UInt8}(flow_graph), DiGraph{Int16}(flow_graph)) - for e in flow_edges - u,v,f = e - add_edge!(flow_graph,u,v) - capacity_matrix[u,v] = f - end - - residual_graph = LightGraphs.residual(flow_graph) - - # Test with default distances - @test LightGraphs.edmonds_karp_impl(residual_graph, 1, 8, LightGraphs.DefaultCapacity(residual_graph))[1] == 3 - - # Test with capacity matrix - @test LightGraphs.edmonds_karp_impl(residual_graph,1,8,capacity_matrix)[1] == 28 - - # Test the types of the values returned by fetch_path - function test_find_path_types(residual_graph, s, t, flow_matrix, capacity_matrix) - v, P, S, flag = LightGraphs.fetch_path(residual_graph, s, t, flow_matrix, capacity_matrix) - @test typeof(P) == Vector{Int} - @test typeof(S) == Vector{Int} - @test typeof(flag) == Int - @test typeof(v) == Int - end + capacity_matrix = zeros(Int,8,8) - # Test the value of the flags returned. - function test_find_path_disconnected(residual_graph, s, t, flow_matrix, capacity_matrix) - h = copy(residual_graph) - for dst in collect(neighbors(residual_graph, s)) - rem_edge!(residual_graph, s, dst) - end - v, P, S, flag = LightGraphs.fetch_path(residual_graph, s, t, flow_matrix, capacity_matrix) - @test flag == 1 - for dst in collect(neighbors(h, t)) - rem_edge!(h, t, dst) - end - v, P, S, flag = LightGraphs.fetch_path(h, s, t, flow_matrix, capacity_matrix) - @test flag == 0 - for i in collect(in_neighbors(h, t)) - rem_edge!(h, i, t) + for e in flow_edges + u,v,f = e + add_edge!(fg,u,v) + capacity_matrix[u,v] = f end - v, P, S, flag = LightGraphs.fetch_path(h, s, t, flow_matrix, capacity_matrix) - @test flag == 2 - end - flow_matrix = zeros(Int, nv(residual_graph), nv(residual_graph)) - test_find_path_types(residual_graph, 1,8, flow_matrix, capacity_matrix) - test_find_path_disconnected(residual_graph, 1, 8, flow_matrix, capacity_matrix) + residual_graph = @inferred(LightGraphs.residual(fg)) + + # Test with default distances + @test @inferred(LightGraphs.edmonds_karp_impl(residual_graph, 1, 8, LightGraphs.DefaultCapacity(residual_graph)))[1] == 3 + + # Test with capacity matrix + @test @inferred(LightGraphs.edmonds_karp_impl(residual_graph,1,8,capacity_matrix))[1] == 28 + + # Test the types of the values returned by fetch_path + function test_find_path_types(residual_graph, s, t, flow_matrix, capacity_matrix) + v, P, S, flag = LightGraphs.fetch_path(residual_graph, s, t, flow_matrix, capacity_matrix) + @test typeof(P) == Vector{Int} + @test typeof(S) == Vector{Int} + @test typeof(flag) == Int + @test typeof(v) == eltype(residual_graph) + end + + # Test the value of the flags returned. + function test_find_path_disconnected(residual_graph, s, t, flow_matrix, capacity_matrix) + h = copy(residual_graph) + for dst in collect(neighbors(residual_graph, s)) + rem_edge!(residual_graph, s, dst) + end + v, P, S, flag = LightGraphs.fetch_path(residual_graph, s, t, flow_matrix, capacity_matrix) + @test flag == 1 + for dst in collect(neighbors(h, t)) + rem_edge!(h, t, dst) + end + v, P, S, flag = LightGraphs.fetch_path(h, s, t, flow_matrix, capacity_matrix) + @test flag == 0 + for i in collect(in_neighbors(h, t)) + rem_edge!(h, i, t) + end + v, P, S, flag = LightGraphs.fetch_path(h, s, t, flow_matrix, capacity_matrix) + @test flag == 2 + end + + flow_matrix = zeros(Int, nv(residual_graph), nv(residual_graph)) + test_find_path_types(residual_graph, 1,8, flow_matrix, capacity_matrix) + test_find_path_disconnected(residual_graph, 1, 8, flow_matrix, capacity_matrix) + end end diff --git a/test/flow/maximum_flow.jl b/test/flow/maximum_flow.jl index 0dfe13503..fe7731f89 100644 --- a/test/flow/maximum_flow.jl +++ b/test/flow/maximum_flow.jl @@ -1,54 +1,57 @@ @testset "Maximum flow" begin #### Graphs for testing graphs = [ - # Graph with 8 vertices - (8, - [ - (1,2,10),(1,3,5),(1,4,15),(2,3,4),(2,5,9), - (2,6,15),(3,4,4),(3,6,8),(4,7,16),(5,6,15), - (5,8,10),(6,7,15),(6,8,10),(7,3,6),(7,8,10) - ], - 1,8, # source/target - 3, # answer for default capacity - 28, # answer for custom capacity - 15,5 # answer for restricted capacity/restriction - ), + # Graph with 8 vertices + (8, + [ + (1,2,10),(1,3,5),(1,4,15),(2,3,4),(2,5,9), + (2,6,15),(3,4,4),(3,6,8),(4,7,16),(5,6,15), + (5,8,10),(6,7,15),(6,8,10),(7,3,6),(7,8,10) + ], + 1,8, # source/target + 3, # answer for default capacity + 28, # answer for custom capacity + 15,5 # answer for restricted capacity/restriction + ), - # Graph with 6 vertices - (6, - [ - (1,2,9),(1,3,9),(2,3,10),(2,4,8),(3,4,1), - (3,5,3),(5,4,8),(4,6,10),(5,6,7) - ], - 1,6, # source/target - 2, # answer for default capacity - 12, # answer for custom capacity - 8,5 # answer for restricted capacity/restriction - ) + # Graph with 6 vertices + (6, + [ + (1,2,9),(1,3,9),(2,3,10),(2,4,8),(3,4,1), + (3,5,3),(5,4,8),(4,6,10),(5,6,7) + ], + 1,6, # source/target + 2, # answer for default capacity + 12, # answer for custom capacity + 8,5 # answer for restricted capacity/restriction + ) ] for (nvertices,flow_edges,s,t,fdefault,fcustom,frestrict,caprestrict) in graphs flow_graph = DiGraph(nvertices) - capacity_matrix = zeros(Int,nvertices,nvertices) - for e in flow_edges - u,v,f = e - add_edge!(flow_graph,u,v) - capacity_matrix[u,v] = f - end + for g in testdigraphs(flow_graph) + capacity_matrix = zeros(Int,nvertices,nvertices) + for e in flow_edges + u,v,f = e + add_edge!(g,u,v) + capacity_matrix[u,v] = f + end - # Test DefaultCapacity - d = LightGraphs.DefaultCapacity(flow_graph) - @test typeof(d) <: AbstractArray{Int, 2} - @test d[s,t] == 0 - @test size(d) == (nvertices,nvertices) - @test typeof(transpose(d)) == LightGraphs.DefaultCapacity - @test typeof(ctranspose(d)) == LightGraphs.DefaultCapacity + # Test DefaultCapacity + d = @inferred(LightGraphs.DefaultCapacity(g)) + T = eltype(d) + @test typeof(d) <: AbstractMatrix{T} + @test d[s,t] == 0 + @test size(d) == (nvertices,nvertices) + @test typeof(transpose(d)) <: LightGraphs.DefaultCapacity + @test typeof(ctranspose(d)) <: LightGraphs.DefaultCapacity - # Test all algorithms - for ALGO in [EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm] - @test maximum_flow(flow_graph,s,t,algorithm=ALGO())[1] == fdefault - @test maximum_flow(flow_graph,s,t,capacity_matrix,algorithm=ALGO())[1] == fcustom - @test maximum_flow(flow_graph,s,t,capacity_matrix,algorithm=ALGO(),restriction=caprestrict)[1] == frestrict + # Test all algorithms - type instability in PushRelabel #553 + for ALGO in [EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm] + @test maximum_flow(g,s,t,algorithm=ALGO())[1] == fdefault + @test maximum_flow(g,s,t,capacity_matrix,algorithm=ALGO())[1] == fcustom + @test maximum_flow(g,s,t,capacity_matrix,algorithm=ALGO(),restriction=caprestrict)[1] == frestrict + end end end end diff --git a/test/flow/multiroute_flow.jl b/test/flow/multiroute_flow.jl index b9171faea..8ac5dff1a 100644 --- a/test/flow/multiroute_flow.jl +++ b/test/flow/multiroute_flow.jl @@ -1,86 +1,86 @@ @testset "Multiroute flow" begin #### Graphs for testing graphs = [ - # Graph with 8 vertices - (8, - [ - (1, 2, 10), (1, 3, 5), (1, 4, 15), (2, 3, 4), (2, 5, 9), - (2, 6, 15), (3, 4, 4), (3, 6, 8), (4, 7, 16), (5, 6, 15), - (5, 8, 10), (6, 7, 15), (6, 8, 10), (7, 3, 6), (7, 8, 10), - (8, 1, 8) # Reverse edge to test the slope in EMRF - ], - 1, 8, # source/target - [28, 28, 15, 0], # answer for 1 to 4 route(s) flows - [(0., 0., 3), (5., 15., 2), # breaking points - (10., 25., 1), (13., 28., 0)], - 28. # value for 1.5 routes - ), + # Graph with 8 vertices + (8, + [ + (1, 2, 10), (1, 3, 5), (1, 4, 15), (2, 3, 4), (2, 5, 9), + (2, 6, 15), (3, 4, 4), (3, 6, 8), (4, 7, 16), (5, 6, 15), + (5, 8, 10), (6, 7, 15), (6, 8, 10), (7, 3, 6), (7, 8, 10), + (8, 1, 8) # Reverse edge to test the slope in EMRF + ], + 1, 8, # source/target + [28, 28, 15, 0], # answer for 1 to 4 route(s) flows + [(0., 0., 3), (5., 15., 2), # breaking points + (10., 25., 1), (13., 28., 0)], + 28. # value for 1.5 routes + ), - # Graph with 6 vertices - (6, - [ - (1, 2, 9), (1, 3, 9), (2, 3, 10), (2, 4, 8), (3, 4, 1), - (3, 5, 3), (5, 4, 8), (4, 6, 10), (5, 6, 7) - ], - 1, 6, # source/target - [12, 6, 0], # answer for 1 to 3 route(s) flows - [(0., 0., 2), (3., 6., 1), (9., 12., 0)], # breaking points - 9. # value for 1.5 routes - ), + # Graph with 6 vertices + (6, + [ + (1, 2, 9), (1, 3, 9), (2, 3, 10), (2, 4, 8), (3, 4, 1), + (3, 5, 3), (5, 4, 8), (4, 6, 10), (5, 6, 7) + ], + 1, 6, # source/target + [12, 6, 0], # answer for 1 to 3 route(s) flows + [(0., 0., 2), (3., 6., 1), (9., 12., 0)], # breaking points + 9. # value for 1.5 routes + ), - # Graph with 7 vertices - (7, - [ - (1, 2, 1), (1, 3, 2), (1, 4, 3), (1, 5, 4), (1, 6, 5), - (2, 7, 1), (3, 7, 2), (4, 7, 3), (5, 7, 4), (6, 7, 5) - ], - 1, 7, # source/target - [15, 15, 15, 12, 5, 0], # answer for 1 to 6 route(s) flows - [(0., 0., 5), (1., 5., 4), (2., 9., 3), # breaking points - (3., 12., 2), (4., 14., 1), (5., 15., 0)], - 15. # value for 1.5 routes - ), + # Graph with 7 vertices + (7, + [ + (1, 2, 1), (1, 3, 2), (1, 4, 3), (1, 5, 4), (1, 6, 5), + (2, 7, 1), (3, 7, 2), (4, 7, 3), (5, 7, 4), (6, 7, 5) + ], + 1, 7, # source/target + [15, 15, 15, 12, 5, 0], # answer for 1 to 6 route(s) flows + [(0., 0., 5), (1., 5., 4), (2., 9., 3), # breaking points + (3., 12., 2), (4., 14., 1), (5., 15., 0)], + 15. # value for 1.5 routes + ), - # Graph with 6 vertices - (6, - [ - (1, 2, 1), (1, 3, 1), (1, 4, 2), (1, 5, 2), - (2, 6, 1), (3, 6, 1), (4, 6, 2), (5, 6, 2), - ], - 1, 6, # source/target - [6, 6, 6, 4, 0], # answer for 1 to 5 route(s) flows - [(0., 0., 4), (1., 4., 2), (2., 6., 0)], # breaking points - 6. # value for 1.5 routes - ) + # Graph with 6 vertices + (6, + [ + (1, 2, 1), (1, 3, 1), (1, 4, 2), (1, 5, 2), + (2, 6, 1), (3, 6, 1), (4, 6, 2), (5, 6, 2), + ], + 1, 6, # source/target + [6, 6, 6, 4, 0], # answer for 1 to 5 route(s) flows + [(0., 0., 4), (1., 4., 2), (2., 6., 0)], # breaking points + 6. # value for 1.5 routes + ) ] for (nvertices, flow_edges, s, t, froutes, breakpts, ffloat) in graphs flow_graph = DiGraph(nvertices) - capacity_matrix = zeros(Int, nvertices, nvertices) - for e in flow_edges - u, v, f = e - add_edge!(flow_graph, u, v) - capacity_matrix[u, v] = f - end - - - # Test ExtendedMultirouteFlowAlgorithm when the number of routes is either - # Noninteger or 0 (the algorithm returns the breaking points) - @test multiroute_flow(flow_graph, s, t, capacity_matrix) == breakpts - @test multiroute_flow(flow_graph, s, t, capacity_matrix, routes=1.5)[1] ≈ ffloat - @test multiroute_flow(breakpts, 1.5)[2] ≈ ffloat + for g in testdigraphs(flow_graph) + capacity_matrix = zeros(Int, nvertices, nvertices) + for e in flow_edges + u, v, f = e + add_edge!(g, u, v) + capacity_matrix[u, v] = f + end + # Test ExtendedMultirouteFlowAlgorithm when the number of routes is either + # Noninteger or 0 (the algorithm returns the breaking points) + @test multiroute_flow(g, s, t, capacity_matrix) == breakpts + @test multiroute_flow(g, s, t, capacity_matrix, routes=1.5)[1] ≈ ffloat + @test multiroute_flow(breakpts, 1.5)[2] ≈ ffloat - # Test all other algorithms - for AlgoFlow in [EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm] - # When the input are breaking points and routes number - @test multiroute_flow(breakpts, 1.5, flow_graph, s, t, capacity_matrix)[1] ≈ ffloat - for AlgoMrf in [ExtendedMultirouteFlowAlgorithm, KishimotoAlgorithm] - for (k, val) in enumerate(froutes) - @test multiroute_flow(flow_graph, s, t, capacity_matrix, - flow_algorithm = AlgoFlow(), mrf_algorithm = AlgoMrf(), - routes = k)[1] ≈ val + # Test all other algorithms - PR is unstable - see #553 + for AlgoFlow in [EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm] + # When the input are breaking points and routes number + @test multiroute_flow(breakpts, 1.5, g, s, t, capacity_matrix)[1] ≈ ffloat + for AlgoMrf in [ExtendedMultirouteFlowAlgorithm, KishimotoAlgorithm] + for (k, val) in enumerate(froutes) + @test multiroute_flow(g, s, t, capacity_matrix, + flow_algorithm = AlgoFlow(), mrf_algorithm = AlgoMrf(), + routes = k)[1] ≈ val + end + end end - end end end end diff --git a/test/flow/push_relabel.jl b/test/flow/push_relabel.jl index 1c77bd959..f9525f94c 100644 --- a/test/flow/push_relabel.jl +++ b/test/flow/push_relabel.jl @@ -16,72 +16,72 @@ add_edge!(flow_graph,u,v) capacity_matrix[u,v] = f end + for g in testdigraphs(flow_graph) + residual_graph = @inferred(LightGraphs.residual(g)) - residual_graph = LightGraphs.residual(flow_graph) + # Test enqueue_vertex + Q = Array{Int,1}() + excess = [0, 1, 0, 1] + active = [false, false, true, true] + @test @inferred(LightGraphs.enqueue_vertex!(Q, 1, active, excess)) == nothing + @test @inferred(LightGraphs.enqueue_vertex!(Q, 3, active, excess)) == nothing + @test @inferred(LightGraphs.enqueue_vertex!(Q, 4, active, excess)) == nothing + @test length(Q) == 0 + @test @inferred(LightGraphs.enqueue_vertex!(Q, 2, active, excess)) == nothing + @test length(Q) == 1 - # Test enqueue_vertex - Q = Array{Int,1}() - excess = [0, 1, 0, 1] - active = [false, false, true, true] - @test LightGraphs.enqueue_vertex!(Q, 1, active, excess) == nothing - @test LightGraphs.enqueue_vertex!(Q, 3, active, excess) == nothing - @test LightGraphs.enqueue_vertex!(Q, 4, active, excess) == nothing - @test length(Q) == 0 - @test LightGraphs.enqueue_vertex!(Q, 2, active, excess) == nothing - @test length(Q) == 1 + # Test push_flow + Q = Array{Int,1}() + excess = [15, 1, 1, 0, 0, 0, 0, 0] + height = [8, 0, 0, 0, 0, 0, 0, 0] + active = [true, false, false, false, false, false, false, true] + flow_matrix = zeros(Int, 8, 8) + @test @inferred(LightGraphs.push_flow!(residual_graph, 1, 2, capacity_matrix, flow_matrix, excess, height, active, Q)) == nothing + @test length(Q) == 1 + @test flow_matrix[1,2] == 10 + @test @inferred(LightGraphs.push_flow!(residual_graph, 2, 3, capacity_matrix, flow_matrix, excess, height, active, Q)) == nothing + @test length(Q) == 1 + @test flow_matrix[2,3] == 0 - # Test push_flow - Q = Array{Int,1}() - excess = [15, 1, 1, 0, 0, 0, 0, 0] - height = [8, 0, 0, 0, 0, 0, 0, 0] - active = [true, false, false, false, false, false, false, true] - flow_matrix = zeros(Int, 8, 8) - @test LightGraphs.push_flow!(residual_graph, 1, 2, capacity_matrix, flow_matrix, excess, height, active, Q) == nothing - @test length(Q) == 1 - @test flow_matrix[1,2] == 10 - @test LightGraphs.push_flow!(residual_graph, 2, 3, capacity_matrix, flow_matrix, excess, height, active, Q) == nothing - @test length(Q) == 1 - @test flow_matrix[2,3] == 0 + # Test gap + Q = Array{Int,1}() + excess = [15, 1, 1, 0, 0, 0, 0, 0] + height = [8, 2, 2, 1, 3, 3, 4, 5] + active = [true, false, false, false, false, false, false, true] + count = [0, 1, 2, 2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] + flow_matrix = zeros(Int, 8, 8) - # Test gap - Q = Array{Int,1}() - excess = [15, 1, 1, 0, 0, 0, 0, 0] - height = [8, 2, 2, 1, 3, 3, 4, 5] - active = [true, false, false, false, false, false, false, true] - count = [0, 1, 2, 2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] - flow_matrix = zeros(Int, 8, 8) + @test @inferred(LightGraphs.gap!(residual_graph, 1, excess, height, active, count, Q)) == nothing + @test length(Q) == 2 - @test LightGraphs.gap!(residual_graph, 1, excess, height, active, count, Q) == nothing - @test length(Q) == 2 + # Test relabel + Q = Array{Int,1}() + excess = [15, 1, 1, 0, 0, 0, 0, 0] + height = [8, 1, 1, 1, 1, 1, 1, 0] + active = [true, false, false, false, false, false, false, true] + count = [1, 6, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] + flow_matrix = zeros(Int, 8, 8) - # Test relabel - Q = Array{Int,1}() - excess = [15, 1, 1, 0, 0, 0, 0, 0] - height = [8, 1, 1, 1, 1, 1, 1, 0] - active = [true, false, false, false, false, false, false, true] - count = [1, 6, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] - flow_matrix = zeros(Int, 8, 8) + @test @inferred(LightGraphs.relabel!(residual_graph, 2, capacity_matrix, flow_matrix, excess, height, active, count, Q)) == nothing + @test length(Q) == 1 - @test LightGraphs.relabel!(residual_graph, 2, capacity_matrix, flow_matrix, excess, height, active, count, Q) == nothing - @test length(Q) == 1 + # Test discharge + Q = Array{Int,1}() + excess = [50, 1, 1, 0, 0, 0, 0, 0] + height = [8, 0, 0, 0, 0, 0, 0, 0] + active = [true, false, false, false, false, false, false, true] + count = [7, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] + flow_matrix = zeros(Int, 8, 8) - # Test discharge - Q = Array{Int,1}() - excess = [50, 1, 1, 0, 0, 0, 0, 0] - height = [8, 0, 0, 0, 0, 0, 0, 0] - active = [true, false, false, false, false, false, false, true] - count = [7, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] - flow_matrix = zeros(Int, 8, 8) + @test @inferred(LightGraphs.discharge!(residual_graph, 1, capacity_matrix, flow_matrix, excess, height, active, count, Q)) == nothing + @test length(Q) == 3 - @test LightGraphs.discharge!(residual_graph, 1, capacity_matrix, flow_matrix, excess, height, active, count, Q) == nothing - @test length(Q) == 3 - - # Test with default distances - @test LightGraphs.push_relabel(residual_graph, 1, 8, LightGraphs.DefaultCapacity(residual_graph))[1] == 3 - - # Test with capacity matrix - @test LightGraphs.push_relabel(residual_graph, 1, 8, capacity_matrix)[1] == 28 + # Test with default distances + @test LightGraphs.push_relabel(residual_graph, 1, 8, LightGraphs.DefaultCapacity(residual_graph))[1] == 3 + # Test with capacity matrix + @test LightGraphs.push_relabel(residual_graph, 1, 8, capacity_matrix)[1] == 28 + end # Non regression test added for #448 M448 =[0 1 0 0 1 1 1 0 0 0 1 0 diff --git a/test/generators/euclideangraphs.jl b/test/generators/euclideangraphs.jl index 375ee3430..27be1b6e0 100644 --- a/test/generators/euclideangraphs.jl +++ b/test/generators/euclideangraphs.jl @@ -1,7 +1,7 @@ @testset "Euclidean graphs" begin N = 10 d = 2 - g, weights, points = euclidean_graph(N, d) + g, weights, points = @inferred(euclidean_graph(N, d)) @test nv(g) == N @test ne(g) == N*(N-1) ÷ 2 @test (d,N) == size(points) @@ -10,7 +10,7 @@ @test maximum(points) <= 1 @test minimum(points) >= 0. - g, weights, points = euclidean_graph(N, d, bc=:periodic) + g, weights, points = @inferred(euclidean_graph(N, d, bc=:periodic)) @test maximum(x->x[2], weights) <= sqrt(d/2) @test minimum(x->x[2], weights) >= 0. @test maximum(points) <= 1 diff --git a/test/generators/randgraphs.jl b/test/generators/randgraphs.jl index ad6ecfec6..9c94a74eb 100644 --- a/test/generators/randgraphs.jl +++ b/test/generators/randgraphs.jl @@ -1,4 +1,7 @@ -@testset "Rand graphs" begin +@testset "Randgraphs" begin + r1 = Graph(10,20) + r2 = DiGraph(5,10) + @test nv(r1) == 10 @test ne(r1) == 20 @test nv(r2) == 5 @@ -195,21 +198,31 @@ intra = density * -0.005 noise = density * 0.00501 sbm = nearbipartiteSBM(sizes, between, intra, noise) - edgestream = @task make_edgestream(sbm) + edgestream = make_edgestream(sbm) g = Graph(sum(sizes), numedges, edgestream) return sbm, g end + function test_sbm(sbm, bp) + @test sum(sbm.affinities) != NaN + @test all(sbm.affinities .> 0) + @test sum(sbm.affinities) != 0 + @test all(bp .>= 0) + @test all(bp .!= NaN) + end + numedges = 100 sizes = [10, 10, 10, 10] n = sum(sizes) sbm, g = generate_nbp_sbm(numedges, sizes) + @test ne(g) >= 0.9numedges bc = blockcounts(sbm, g) bp = blockfractions(sbm, g) ./ (sizes * sizes') ratios = bp ./ (sbm.affinities ./ sum(sbm.affinities)) - @test norm(ratios) < 0.25 + test_sbm(sbm, bp) + @test norm(collect(ratios)) < 0.25 sizes = [200, 200, 100] internaldeg = 15 @@ -220,12 +233,14 @@ numedges *= div(sum(sizes), 2) sbm = StochasticBlockModel(internalp, externalp, sizes) g = Graph(sum(sizes), numedges, sbm) + @test ne(g) >= 0.9numedges @test ne(g) <= numedges @test nv(g) == sum(sizes) bc = blockcounts(sbm, g) bp = blockfractions(sbm, g) ./ (sizes * sizes') + test_sbm(sbm, bp) ratios = bp ./ (sbm.affinities ./ sum(sbm.affinities)) - @test norm(ratios) < 0.25 + @test norm(collect(ratios)) < 0.25 # check that average degree is not too high # factor of two is cushion for random process @@ -240,4 +255,5 @@ @test sbm == sbm2 sbm.affinities[1,1] = 0 @test sbm != sbm2 + end diff --git a/test/generators/staticgraphs.jl b/test/generators/staticgraphs.jl index 6766e7e86..2a4836aa8 100644 --- a/test/generators/staticgraphs.jl +++ b/test/generators/staticgraphs.jl @@ -1,45 +1,52 @@ @testset "Static graphs" begin - g = CompleteDiGraph(5) + g = @inferred(CompleteDiGraph(5)) @test nv(g) == 5 && ne(g) == 20 - g = CompleteGraph(5) + g = @inferred(CompleteGraph(5)) @test nv(g) == 5 && ne(g) == 10 - g = CompleteBipartiteGraph(5, 8) + g = @inferred(CompleteBipartiteGraph(5, 8)) @test nv(g) == 13 && ne(g) == 40 - g = StarDiGraph(5) + g = @inferred(StarDiGraph(5)) @test nv(g) == 5 && ne(g) == 4 - g = StarGraph(5) + g = @inferred(StarGraph(5)) @test nv(g) == 5 && ne(g) == 4 - g = StarGraph(1) + g = @inferred(StarGraph(1)) @test nv(g) == 1 && ne(g) == 0 - g = PathDiGraph(5) + g = @inferred(PathDiGraph(5)) @test nv(g) == 5 && ne(g) == 4 - g = PathGraph(5) + g = @inferred(PathGraph(5)) @test nv(g) == 5 && ne(g) == 4 - g = CycleDiGraph(5) + g = @inferred(CycleDiGraph(5)) @test nv(g) == 5 && ne(g) == 5 - g = CycleGraph(5) + g = @inferred(CycleGraph(5)) @test nv(g) == 5 && ne(g) == 5 - g = WheelDiGraph(5) + g = @inferred(WheelDiGraph(5)) @test nv(g) == 5 && ne(g) == 8 - g = WheelGraph(5) + g = @inferred(WheelGraph(5)) @test nv(g) == 5 && ne(g) == 8 - g = Grid([3,3,4]) + g = @inferred(Grid([3,3,4])) @test nv(g) == 3*3*4 @test ne(g) == 75 - @test maximum(degree(g)) == 6 - @test minimum(degree(g)) == 3 + @test Δ(g) == 6 + @test δ(g) == 3 - g = CliqueGraph(3,5) + g = @inferred(Grid([3,3,4], periodic=true)) + @test nv(g) == 3*3*4 + @test ne(g) == 108 + @test Δ(g) == 6 + @test δ(g) == 6 + + + g = @inferred(CliqueGraph(3,5)) @test nv(g) == 15 && ne(g) == 20 @test g[1:3] == CompleteGraph(3) - g = crosspath(3, BinaryTree(2)) + g = @inferred(crosspath(3, BinaryTree(2))) # f = Vector{Vector{Int}}[[2 3 4]; # [1 5]; # [1 6]; @@ -56,7 +63,7 @@ Adj = sparse(I,J,V) @test Adj == sparse(g) - g = DoubleBinaryTree(3) + g = @inferred(DoubleBinaryTree(3)) # [[3,2,8] # [4,1,5] # [1,6,7] @@ -77,7 +84,7 @@ Adj = sparse(I,J,V) @test Adj == sparse(g) - rg3 = RoachGraph(3) + rg3 = @inferred(RoachGraph(3)) # [3] # [4] # [1,5] diff --git a/test/graphdigraph.jl b/test/graphdigraph.jl deleted file mode 100644 index 52fd0b4ef..000000000 --- a/test/graphdigraph.jl +++ /dev/null @@ -1,77 +0,0 @@ -@testset "Graph/DiGraph" begin - @test sprint(show, h1) == "{5, 0} undirected graph" - @test sprint(show, h3) == "empty undirected graph" - - @test Graph(DiGraph(g3)) == g3 - - @test degree(g3, 1) == 1 - # @test all_neighbors(g3, 3) == [2, 4] - @test density(g3) == 0.4 - - g = Graph(5) - @test add_edge!(g, 1, 2) - - e2 = Edge(1,3) - e3 = Edge(1,4) - e4 = Edge(2,5) - e5 = Edge(3,5) - - - @test add_edge!(g, e2) - @test add_edge!(g, e3) - @test add_edge!(g, e4) - @test add_edge!(g, e5) - - h = DiGraph(5) - @test add_edge!(h, 1, 2) - @test add_edge!(h, e2) - @test add_edge!(h, e3) - @test add_edge!(h, e4) - @test add_edge!(h, e5) - - @test LightGraphs.fadj(g)[1] == LightGraphs.fadj(g,1) == - LightGraphs.badj(g)[1] == LightGraphs.badj(g,1) == - LightGraphs.adj(g)[1] == LightGraphs.adj(g,1) == - [2,3,4] - - @test sprint(show, h4) == "{7, 0} directed graph" - @test sprint(show, h5) == "empty directed graph" - @test has_edge(g, e1) - @test has_edge(h, e1) - @test !has_edge(g, e0) - @test !has_edge(h, e0) - - @test degree(g4, 1) == 1 - # @test all_neighbors(g4, 3) == [4] - @test density(g4) == 0.2 - - @test nv(a1) == 3 - @test ne(a1) == 2 - @test nv(a2) == 3 - @test ne(a2) == 5 - - badadjmx = [ 0 1 0; 1 0 1] - @test_throws ErrorException Graph(badadjmx) - @test_throws ErrorException Graph(sparse(badadjmx)) - @test_throws ErrorException DiGraph(badadjmx) - @test_throws ErrorException DiGraph(sparse(badadjmx)) - @test_throws ErrorException Graph([1 0; 1 1]) - - - @test !add_edge!(g, 100, 100) - @test !add_edge!(h, 100, 100) - - @test_throws ErrorException Graph(sparse(adjmx2)) - - g = Graph(sparse(adjmx1)) - h = DiGraph(sparse(adjmx1)) - @test !is_directed(g) - @test is_directed(h) - - - @test (nv(g), ne(g)) == (3, 2) - @test (nv(h), ne(h)) == (3, 4) - @test Graph(h) == g - - @test sort(all_neighbors(WheelDiGraph(10),2)) == [1, 3, 10] -end diff --git a/test/graphtypes/simplegraphs/simpleedge.jl b/test/graphtypes/simplegraphs/simpleedge.jl new file mode 100644 index 000000000..6d3601064 --- /dev/null +++ b/test/graphtypes/simplegraphs/simpleedge.jl @@ -0,0 +1,29 @@ +import LightGraphs.SimpleGraphs.SimpleEdge +@testset "SimpleEdge" begin + e = SimpleEdge(1,2) + re = SimpleEdge(2,1) + + for s in [0x01, UInt16(1), 1] + T = typeof(s) + d = s+one(T) + p = Pair(s, d) + + ep = SimpleEdge(p) + t1 = (s, d) + t2 = (s, d, "foo") + + @test src(ep) == s + @test dst(ep) == s + one(T) + + @test eltype(p) == typeof(s) + @test SimpleEdge(p) == e + @test SimpleEdge(t1) == SimpleEdge(t2) == e + + @test SimpleEdge{Int64}(ep) == e + + @test Pair(e) == p + @test Tuple(e) == t1 + @test reverse(ep) == re + @test sprint(show, ep) == "Edge 1 => 2" + end +end diff --git a/test/graphtypes/simplegraphs/simpleedgeiter.jl b/test/graphtypes/simplegraphs/simpleedgeiter.jl new file mode 100644 index 000000000..5c47bc0ef --- /dev/null +++ b/test/graphtypes/simplegraphs/simpleedgeiter.jl @@ -0,0 +1,55 @@ +@testset "SimpleEdgeIter" begin + ga = @inferred(SimpleGraph(10,20; seed=1)) + gb = @inferred(SimpleGraph(10,20; seed=1)) + @test sprint(show,edges(ga)) == "SimpleEdgeIter 20" + @test sprint(show, start(edges(ga))) == "SimpleEdgeIterState [1, 1, false]" + + @test length(collect(edges(Graph(0,0)))) == 0 + + @test @inferred(edges(ga)) == edges(gb) + @test @inferred(edges(ga)) == collect(Edge, edges(gb)) + @test collect(Edge, edges(gb)) == edges(ga) + @test Set{Edge}(collect(Edge, edges(gb))) == edges(ga) + @test @inferred(edges(ga)) == Set{Edge}(collect(Edge, edges(gb))) + + @test @inferred(eltype(edges(ga))) == eltype(typeof(edges(ga))) == SimpleEdge + + ga = @inferred(SimpleDiGraph(10,20; seed=1)) + gb = @inferred(SimpleDiGraph(10,20; seed=1)) + @test @inferred(edges(ga)) == edges(gb) + @test @inferred(edges(ga)) == collect(SimpleEdge, edges(gb)) + @test collect(SimpleEdge, edges(gb)) == edges(ga) + @test Set{Edge}(collect(SimpleEdge, edges(gb))) == edges(ga) + @test @inferred(edges(ga)) == Set{SimpleEdge}(collect(SimpleEdge, edges(gb))) + + ga = SimpleGraph(10) + add_edge!(ga, 3, 2) + add_edge!(ga, 3, 10) + add_edge!(ga, 5, 10) + add_edge!(ga, 10, 3) + + eit = edges(ga) + es = @inferred(start(eit)) + + @test es.s == 2 + @test es.di == 1 + + @test [e for e in eit] == [Edge(2, 3), Edge(3, 10), Edge(5,10)] + + ga = SimpleDiGraph(10) + add_edge!(ga, 3, 2) + add_edge!(ga, 3, 10) + add_edge!(ga, 5, 10) + add_edge!(ga, 10, 3) + + eit = @inferred(edges(ga)) + es = @inferred(start(eit)) + + @test es.s == 3 + @test es.di == 1 + + @test [e for e in eit] == [ + SimpleEdge(3, 2), SimpleEdge(3, 10), + SimpleEdge(5,10), SimpleEdge(10,3) + ] +end diff --git a/test/graphtypes/simplegraphs/simplegraphs.jl b/test/graphtypes/simplegraphs/simplegraphs.jl new file mode 100644 index 000000000..f51d20099 --- /dev/null +++ b/test/graphtypes/simplegraphs/simplegraphs.jl @@ -0,0 +1,159 @@ +importall LightGraphs.SimpleGraphs +import LightGraphs.SimpleGraphs: fadj, badj, adj +struct DummySimpleGraph <: AbstractSimpleGraph end +@testset "SimpleGraphs" begin + adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph + adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph + # specific concrete generators - no need for loop + @test @inferred(eltype(SimpleGraph())) == Int + @test @inferred(eltype(SimpleGraph(adjmx1))) == Int + @test_throws ErrorException SimpleGraph(adjmx2) + + @test_throws ErrorException badj(DummySimpleGraph()) + + @test @inferred(ne(SimpleGraph(PathDiGraph(5)))) == 4 + @test @inferred(!is_directed(SimpleGraph)) + + @test @inferred(eltype(SimpleDiGraph())) == Int + @test @inferred(eltype(SimpleDiGraph(adjmx2))) == Int + @test @inferred(ne(SimpleDiGraph(PathGraph(5)))) == 8 + @test @inferred(is_directed(SimpleDiGraph)) + + + for gbig in [Graph(0xff), DiGraph(0xff)] + @test @inferred(!add_vertex!(gbig)) # overflow + @test @inferred(!add_vertices!(gbig, 10)) + end + + gdx = PathDiGraph(4) + gx = SimpleGraph() + for g in testgraphs(gx) + T = eltype(g) + @test sprint(show, g) == "empty undirected simple $T graph" + @inferred(add_vertices!(g, 5)) + @test sprint(show, g) == "{5, 0} undirected simple $T graph" + end + gx = SimpleDiGraph() + for g in testdigraphs(gx) + T = eltype(g) + @test sprint(show, g) == "empty directed simple $T graph" + @inferred(add_vertices!(g, 5)) + @test sprint(show, g) == "{5, 0} directed simple $T graph" + end + + gx = PathGraph(4) + for g in testgraphs(gx) + @test @inferred(vertices(g)) == 1:4 + @test Edge(2,3) in edges(g) + @test @inferred(nv(g)) == 4 + @test @inferred(fadj(g)) == badj(g) == adj(g) == g.fadjlist + @test @inferred(fadj(g,2)) == badj(g,2) == adj(g,2) == g.fadjlist[2] + + @test @inferred(has_edge(g, 2, 3)) + @test @inferred(has_edge(g, 3, 2)) + gc = copy(g) + @test add_edge!(gc, 4, 1) && gc == CycleGraph(4) + + @test @inferred(in_neighbors(g, 2)) == @inferred(out_neighbors(g, 2)) == @inferred(neighbors(g,2)) == [1,3] + @test @inferred(add_vertex!(gc)) # out of order, but we want it for issubset + @test @inferred(g ⊆ gc) + @test @inferred(has_vertex(gc, 5)) + + @test @inferred(ne(g)) == 3 + + @test @inferred(rem_edge!(gc, 1, 2)) && @inferred(!has_edge(gc, 1, 2)) + ga = @inferred(copy(g)) + @test @inferred(rem_vertex!(ga, 2)) && ne(ga) == 1 + @test @inferred(!rem_vertex!(ga, 10)) + + @test @inferred(zero(g)) == SimpleGraph{eltype(g)}() + + # concrete tests below + + @test @inferred(eltype(g)) == eltype(fadj(g,1)) == eltype(nv(g)) + T = @inferred(eltype(g)) + @test @inferred(nv(SimpleGraph{T}(6))) == 6 + + @test @inferred(eltype(SimpleGraph(T))) == T + @test @inferred(eltype(SimpleGraph{T}(adjmx1))) == T + + ga = SimpleGraph(10) + @test @inferred(eltype(SimpleGraph{T}(ga))) == T + + for gd in testdigraphs(gdx) + U = eltype(gd) + @test @inferred(eltype(SimpleGraph(gd))) == U + end + + @test @inferred(edgetype(g)) == SimpleGraphEdge{T} + @test @inferred(copy(g)) == g + @test @inferred(!is_directed(g)) + + e = first(edges(g)) + @test @inferred(has_edge(g, e)) + end + + gdx = PathDiGraph(4) + for g in testdigraphs(gdx) + @test @inferred(vertices(g)) == 1:4 + @test Edge(2,3) in edges(g) + @test !(Edge(3,2) in edges(g)) + @test @inferred(nv(g)) == 4 + @test @inferred(fadj(g)[2]) == fadj(g, 2) == [3] + @test @inferred(badj(g)[2]) == badj(g, 2) == [1] + @test_throws MethodError adj(g) + + @test @inferred(has_edge(g, 2, 3)) + @test @inferred(!has_edge(g, 3, 2)) + gc = @inferred(copy(g)) + @test @inferred(add_edge!(gc, 4, 1)) && gc == CycleDiGraph(4) + + @test @inferred(in_neighbors(g, 2)) == [1] + @test @inferred(out_neighbors(g, 2)) == @inferred(neighbors(g,2)) == [3] + @test @inferred(add_vertex!(gc)) # out of order, but we want it for issubset + @test @inferred(g ⊆ gc) + @test @inferred(has_vertex(gc, 5)) + + @test @inferred(ne(g)) == 3 + + @test @inferred(!rem_edge!(gc, 2, 1)) + @test @inferred(rem_edge!(gc, 1, 2)) && @inferred(!has_edge(gc, 1, 2)) + ga = @inferred(copy(g)) + @test @inferred(rem_vertex!(ga, 2)) && ne(ga) == 1 + @test @inferred(!rem_vertex!(ga, 10)) + + @test @inferred(zero(g)) == SimpleDiGraph{eltype(g)}() + + # concrete tests below + + @test @inferred(eltype(g)) == eltype(@inferred(fadj(g,1))) == eltype(nv(g)) + T = @inferred(eltype(g)) + @test @inferred(nv(SimpleDiGraph{T}(6))) == 6 + + @test @inferred(eltype(SimpleDiGraph(T))) == T + @test @inferred(eltype(SimpleDiGraph{T}(adjmx2))) == T + + ga = SimpleDiGraph(10) + @test @inferred(eltype(SimpleDiGraph{T}(ga))) == T + + for gu in testgraphs(gx) + U = @inferred(eltype(gu)) + @test @inferred(eltype(SimpleDiGraph(gu))) == U + end + + @test @inferred(edgetype(g)) == SimpleDiGraphEdge{T} + @test @inferred(copy(g)) == g + @test @inferred(is_directed(g)) + + e = first(@inferred(edges(g))) + @test @inferred(has_edge(g, e)) + end + + gdx = CompleteDiGraph(4) + for g in testdigraphs(gdx) + @test rem_vertex!(g, 2) + @test nv(g) == 3 && ne(g) == 6 + end + + +end diff --git a/test/interface.jl b/test/interface.jl new file mode 100644 index 000000000..3ecf45fbc --- /dev/null +++ b/test/interface.jl @@ -0,0 +1,38 @@ +mutable struct DummyGraph <: AbstractGraph end +mutable struct DummyDiGraph <: AbstractGraph end +mutable struct DummyEdge <: AbstractEdge end + +@testset "Interface" begin + dummygraph = DummyGraph() + dummydigraph = DummyDiGraph() + dummyedge = DummyEdge() + + @test_throws ErrorException is_directed(DummyGraph) + + for edgefun in [src, dst, Pair, Tuple, reverse] + @test_throws ErrorException edgefun(dummyedge) + end + + for edgefun2edges in [ == ] + @test_throws ErrorException edgefun2edges(dummyedge, dummyedge) + end + + for graphfunbasic in [ + nv, ne, vertices, edges, is_directed, + add_vertex!, edgetype, eltype, zero + ] + @test_throws ErrorException graphfunbasic(dummygraph) + end + + for graphfun1int in [ + rem_vertex!, has_vertex, in_neighbors, out_neighbors + ] + @test_throws ErrorException graphfun1int(dummygraph, 1) + end + for graphfunedge in [ + has_edge, add_edge!, rem_edge! + ] + @test_throws ErrorException graphfunedge(dummygraph, dummyedge) + end + +end # testset diff --git a/test/linalg/graphmatrices.jl b/test/linalg/graphmatrices.jl index 977091f03..6d22bc26b 100644 --- a/test/linalg/graphmatrices.jl +++ b/test/linalg/graphmatrices.jl @@ -28,6 +28,8 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth converttest(SparseMatrix{Float64},stochmat) converttest(SparseMatrix{Float64},adjhat) converttest(SparseMatrix{Float64},avgmat) + @test isa(CombinatorialAdjacency(adjmat), CombinatorialAdjacency) + @test isa(CombinatorialAdjacency(avgmat), CombinatorialAdjacency) @test prescalefactor(adjhat) == postscalefactor(adjhat) @test postscalefactor(stochmat) == prescalefactor(avgmat) @test prescalefactor(adjhat) == postscalefactor(adjhat) @@ -43,22 +45,18 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth @test typeof(StochasticAdjacency(adj)) <: StochasticAdjacency @test typeof(NormalizedAdjacency(adj)) <: NormalizedAdjacency @test typeof(AveragingAdjacency(adj)) <: AveragingAdjacency - if VERSION >= v"0.4" - @test typeof(adjacency(lapl)) <: CombinatorialAdjacency - converttest(SparseMatrix{Float64},lapl) - else - @test adjacency(lapl) != nothing - @test sparse(lapl) != nothing - end + + @test typeof(adjacency(lapl)) <: CombinatorialAdjacency + converttest(SparseMatrix{Float64},lapl) adjmat, stochmat, adjhat, avgmat = constructors(mat) @test typeof(adjacency(lapl)) <: CombinatorialAdjacency - stochlapl = StochasticLaplacian(StochasticAdjacency{Float64}(adjmat)) + stochlapl = StochasticLaplacian(StochasticAdjacency(adjmat)) @test typeof(adjacency(stochlapl)) <: StochasticAdjacency - averaginglapl = AveragingLaplacian(AveragingAdjacency{Float64}(adjmat)) + averaginglapl = AveragingLaplacian(AveragingAdjacency(adjmat)) @test typeof(adjacency(averaginglapl)) <: AveragingAdjacency - normalizedlapl = NormalizedLaplacian(NormalizedAdjacency{Float64}(adjmat)) + normalizedlapl = NormalizedLaplacian(NormalizedAdjacency(adjmat)) @test typeof(adjacency(normalizedlapl)) <: NormalizedAdjacency @test !( typeof(adjacency(normalizedlapl)) <: CombinatorialAdjacency) @@ -69,7 +67,7 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth @test_throws MethodError AveragingLaplacian(lapl) @test_throws MethodError convert(CombinatorialAdjacency, lapl) L = convert(SparseMatrix{Float64}, lapl) - @test sum(abs(sum(L,1))) == 0 + @test sum(abs, (sum(L,1))) == 0 end function test_accessors(mat, n) @@ -92,17 +90,17 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth lapl = CombinatorialLaplacian(adjmat) onevec = ones(Float64, n) v = adjmat*ones(Float64, n) - @test sum(abs(adjmat*onevec)) > 0.0 - @test_approx_eq sum(abs(stochmat*onevec/sum(onevec))) 1.0 - @test sum(abs(lapl*onevec)) == 0 - g(a) = sum(abs(sum(sparse(a),1))) + @test sum(abs, (adjmat*onevec)) > 0.0 + @test sum(abs, ((stochmat * onevec) / sum(onevec))) ≈ 1.0 + @test sum(abs, (lapl*onevec)) == 0 + g(a) = sum(abs, (sum(sparse(a),1))) @test g(lapl) == 0 @test g(NormalizedLaplacian(adjhat)) > 1e-13 @test g(StochasticLaplacian(stochmat)) > 1e-13 @test eigs(adjmat, which=:LR)[1][1] > 1.0 - @test_approx_eq eigs(stochmat, which=:LR)[1][1] 1.0 - @test_approx_eq eigs(avgmat, which=:LR)[1][1] 1.0 + @test eigs(stochmat, which=:LR)[1][1] ≈ 1.0 + @test eigs(avgmat, which=:LR)[1][1] ≈ 1.0 @test eigs(lapl, which=:LR)[1][1] > 2.0 @test_throws MethodError eigs(lapl, which=:SM)[1][1] # --> greater_than(-0.0) lhat = NormalizedLaplacian(adjhat) @@ -116,10 +114,10 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth @test size(lapl, 2) == n @test size(lapl, 3) == 1 - @test_throws MethodError symmetrize(StochasticAdjacency{Float64}(adjmat)) - @test_throws MethodError symmetrize(AveragingAdjacency{Float64}(adjmat)) - @test !issymmetric(AveragingAdjacency{Float64}(adjmat)) - @test !issymmetric(StochasticAdjacency{Float64}(adjmat)) + @test_throws MethodError symmetrize(StochasticAdjacency(adjmat)) + @test_throws MethodError symmetrize(AveragingAdjacency(adjmat)) + @test !issymmetric(AveragingAdjacency(adjmat)) + @test !issymmetric(StochasticAdjacency(adjmat)) @test_throws MethodError symmetrize(NormalizedAdjacency(adjmat)).A # --> adjmat.A begin @@ -142,8 +140,8 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth @test size(lapl, 2) == n @test size(lapl, 3) == 1 - @test_throws MethodError symmetrize(StochasticAdjacency{Float64}(adjmat)) - @test_throws MethodError symmetrize(AveragingAdjacency{Float64}(adjmat)) + @test_throws MethodError symmetrize(StochasticAdjacency(adjmat)) + @test_throws MethodError symmetrize(AveragingAdjacency(adjmat)) @test_throws MethodError symmetrize(NormalizedAdjacency(adjmat)).A # --> adjmat.A @test symmetrize(adjmat).A == adjmat.A # these tests are basically the code @@ -158,51 +156,35 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth adjmat = CombinatorialAdjacency(mat) ahatp = PunchedAdjacency(adjmat) y = ahatp * perron(ahatp) - @test_approx_eq_eps dot(y, ahatp.perron) 0.0 1e-8 - @test_approx_eq_eps sumabs(y) 0.0 1e-8 + @test dot(y, ahatp.perron) ≈ 0.0 atol=1.0e-8 + @test sum(abs, y) ≈ 0.0 atol=1.0e-8 eval, evecs = eigs(ahatp, which=:LM) @test eval[1]-1 <= 0 - @test_approx_eq_eps dot(perron(ahatp), evecs[:,1]) 0.0 1e-8 + @test dot(perron(ahatp), evecs[:,1]) ≈ 0.0 atol=1e-8 ahat = ahatp.A @test isa(ahat, NormalizedAdjacency) z = ahatp * perron(ahat) - @test_approx_eq_eps norm(z) 0.0 1e-8 + @test norm(z) ≈ 0.0 atol=1e-8 end - begin - n = 10 - mat = sparse(spones(sprand(n,n,0.3))) - begin - test_adjacency(mat) - end - begin - test_laplacian(mat) - end - - begin - test_accessors(mat, n) - end - end + n = 10 + mat = sparse(spones(sprand(n,n,0.3))) + test_adjacency(mat) + test_laplacian(mat) + test_accessors(mat, n) - begin - n = 10 - mat = symmetrize(sparse(spones(sprand(n,n,0.3)))) - test_arithmetic(mat, n) - end + mat = symmetrize(sparse(spones(sprand(n,n,0.3)))) + test_arithmetic(mat, n) + test_other(mat, n) + test_symmetry(mat, n) + test_punchedmatrix(mat, n) - begin - n = 10 - mat = symmetrize(sparse(spones(sprand(n,n,0.3)))) - test_other(mat, n) - test_symmetry(mat, n) - test_punchedmatrix(mat, n) - end - @doc "Computes the stationary distribution of a random walk" -> + """Computes the stationary distribution of a random walk""" function stationarydistribution(R::StochasticAdjacency; kwargs...) er = eigs(R, nev=1, which=:LR; kwargs...) l1 = er[1][1] @@ -222,14 +204,12 @@ export test_adjacency, test_laplacian, test_accessors, test_arithmetic, test_oth end # Random walk demo - begin - n = 100 - p = 16/n - M = sprand(n,n, p) - M.nzval[:] = 1.0 - A = CombinatorialAdjacency(M) - sd = stationarydistribution(A; ncv=10) - @test all(sd.>=0) - end - end + n = 100 + p = 16/n + M = sprand(n,n, p) + M.nzval[:] = 1.0 + A = CombinatorialAdjacency(M) + sd = stationarydistribution(A; ncv=10) + @test all(sd.>=0) +end end diff --git a/test/linalg/spectral.jl b/test/linalg/spectral.jl index 51b6d289d..91f69bb9b 100644 --- a/test/linalg/spectral.jl +++ b/test/linalg/spectral.jl @@ -4,136 +4,161 @@ import Base: full full(nbt::Nonbacktracking) = full(sparse(nbt)) @testset "Spectral" begin - @test adjacency_matrix(g3)[3,2] == 1 - @test adjacency_matrix(g3)[2,4] == 0 - @test laplacian_matrix(g3)[3,2] == -1 - @test laplacian_matrix(g3)[1,3] == 0 - @test laplacian_spectrum(g3)[5] == 3.6180339887498945 - @test adjacency_spectrum(g3)[1] == -1.732050807568878 + g3 = PathGraph(5) + g4 = PathDiGraph(5) g5 = DiGraph(4) + for g in testgraphs(g3) + @test adjacency_matrix(g)[3,2] == 1 + @test adjacency_matrix(g)[2,4] == 0 + @test laplacian_matrix(g)[3,2] == -1 + @test laplacian_matrix(g)[1,3] == 0 + @test laplacian_spectrum(g)[5] == 3.6180339887498945 + @test adjacency_spectrum(g)[1] == -1.732050807568878 + end + + add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) - @test laplacian_spectrum(g5)[3] == laplacian_spectrum(g5,:both)[3] == 3.0 - @test laplacian_spectrum(g5,:in)[3] == 1.0 - @test laplacian_spectrum(g5,:out)[3] == 1.0 + for g in testdigraphs(g5) + @test laplacian_spectrum(g)[3] == laplacian_spectrum(g,:both)[3] == 3.0 + @test laplacian_spectrum(g,:in)[3] == 1.0 + @test laplacian_spectrum(g,:out)[3] == 1.0 + end # check adjacency matrices with self loops - g = copy(g3) - add_edge!(g,1,1) - @test adjacency_matrix(g)[1,1] == 2 + gx = copy(g3) + add_edge!(gx,1,1) + for g in testgraphs(gx) + @test adjacency_matrix(g)[1,1] == 2 + end g10 = CompleteGraph(10) - B, em = non_backtracking_matrix(g10) - @test length(em) == 2*ne(g10) - @test size(B) == (2*ne(g10),2*ne(g10)) - for i=1:10 - @test sum(B[:,i]) == 8 - @test sum(B[i,:]) == 8 + for g in testgraphs(g10) + B, em = non_backtracking_matrix(g) + @test length(em) == 2*ne(g) + @test size(B) == (2*ne(g),2*ne(g)) + for i=1:10 + @test sum(B[:,i]) == 8 + @test sum(B[i,:]) == 8 + end + @test !issymmetric(B) + + v = ones(Float64, ne(g)) + z = zeros(Float64, nv(g)) + n10 = Nonbacktracking(g) + @test size(n10) == (2*ne(g), 2*ne(g)) + @test eltype(n10) == Float64 + @test !issymmetric(n10) + + LightGraphs.contract!(z, n10, v) + + zprime = contract(n10, v) + @test z == zprime + @test z == 9*ones(Float64, nv(g)) end - @test !issymmetric(B) - - v = ones(Float64, ne(g10)) - z = zeros(Float64, nv(g10)) - n10 = Nonbacktracking(g10) - @test size(n10) == (2*ne(g10), 2*ne(g10)) - @test eltype(n10) == Float64 - @test !issymmetric(n10) - LightGraphs.contract!(z, n10, v) - - zprime = contract(n10, v) - @test z == zprime - @test z == 9*ones(Float64, nv(g10)) - - @test_approx_eq_eps(adjacency_spectrum(g5)[3],0.311, 0.001) + for g in testdigraphs(g5) + @test adjacency_spectrum(g)[3] ≈ 0.311 atol=0.001 + end - @test adjacency_matrix(g3) == - adjacency_matrix(g3, :out) == - adjacency_matrix(g3, :in) == - adjacency_matrix(g3, :both) + for g in testgraphs(g3) + @test adjacency_matrix(g) == + adjacency_matrix(g, :out) == + adjacency_matrix(g, :in) == + adjacency_matrix(g, :both) - @test_throws ErrorException adjacency_matrix(g3, :purple) + @test_throws ErrorException adjacency_matrix(g, :purple) + end #that call signature works - inmat = adjacency_matrix(g5, :in, Int) - outmat = adjacency_matrix(g5, :out, Int) - bothmat = adjacency_matrix(g5, :both, Int) + for g in testdigraphs(g5) + inmat = adjacency_matrix(g, :in, Int) + outmat = adjacency_matrix(g, :out, Int) + bothmat = adjacency_matrix(g, :both, Int) #relations that should be true - @test inmat' == outmat - @test all((bothmat - outmat) .>= 0) - @test all((bothmat - inmat) .>= 0) - - #check properties of the undirected laplacian carry over. - for dir in [:in, :out, :both] - amat = adjacency_matrix(g5, dir, Float64) - lmat = laplacian_matrix(g5, dir, Float64) + @test inmat' == outmat + @test all((bothmat - outmat) .>= 0) + @test all((bothmat - inmat) .>= 0) + + #check properties of the undirected laplacian carry over. + for dir in [:in, :out, :both] + amat = adjacency_matrix(g, dir, Float64) + lmat = laplacian_matrix(g, dir, Float64) @test isa(amat, SparseMatrixCSC{Float64, Int64}) @test isa(lmat, SparseMatrixCSC{Float64, Int64}) evals = eigvals(full(lmat)) @test all(evals .>= -1e-15) # positive semidefinite - @test_approx_eq_eps minimum(evals) 0 1e-13 + @test (minimum(evals)) ≈ 0 atol=1e-13 + end end + for g in testdigraphs(g4) # testing incidence_matrix, first directed graph - @test size(incidence_matrix(g4)) == (5,4) - @test incidence_matrix(g4)[1,1] == -1 - @test incidence_matrix(g4)[2,1] == 1 - @test incidence_matrix(g4)[3,1] == 0 + @test size(incidence_matrix(g)) == (5,4) + @test incidence_matrix(g)[1,1] == -1 + @test incidence_matrix(g)[2,1] == 1 + @test incidence_matrix(g)[3,1] == 0 + end + + for g in testgraphs(g3) # now undirected graph - @test size(incidence_matrix(g3)) == (5,4) - @test incidence_matrix(g3)[1,1] == 1 - @test incidence_matrix(g3)[2,1] == 1 - @test incidence_matrix(g3)[3,1] == 0 + @test size(incidence_matrix(g)) == (5,4) + @test incidence_matrix(g)[1,1] == 1 + @test incidence_matrix(g)[2,1] == 1 + @test incidence_matrix(g)[3,1] == 0 # undirected graph with orientation - @test size(incidence_matrix(g3; oriented=true)) == (5,4) - @test incidence_matrix(g3; oriented=true)[1,1] == -1 - @test incidence_matrix(g3; oriented=true)[2,1] == 1 - @test incidence_matrix(g3; oriented=true)[3,1] == 0 - + @test size(incidence_matrix(g; oriented=true)) == (5,4) + @test incidence_matrix(g; oriented=true)[1,1] == -1 + @test incidence_matrix(g; oriented=true)[2,1] == 1 + @test incidence_matrix(g; oriented=true)[3,1] == 0 + end # TESTS FOR Nonbacktracking operator. n = 10; k = 5 pg = CompleteGraph(n) # ϕ1 = nonbacktrack_embedding(pg, k)' - - nbt = Nonbacktracking(pg) - B, emap = non_backtracking_matrix(pg) - Bs = sparse(nbt) - @test sparse(B) == Bs - @test_approx_eq_eps(eigs(nbt, nev=1)[1], eigs(B, nev=1)[1], 1e-5) - - # check that matvec works - x = ones(Float64, nbt.m) - y = nbt * x - z = B * x - @test norm(y-z) < 1e-8 - - #check that matmat works and full(nbt) == B - @test norm(nbt*eye(nbt.m) - B) < 1e-8 - - #check that matmat works and full(nbt) == B - @test norm(nbt*eye(nbt.m) - B) < 1e-8 - - #check that we can use the implicit matvec in nonbacktrack_embedding - @test size(y) == size(x) - - B₁ = Nonbacktracking(g10) - - @test full(B₁) == full(B) - @test B₁ * ones(size(B₁)[2]) == B*ones(size(B)[2]) - @test size(B₁) == size(B) - # @test_approx_eq_eps norm(eigs(B₁)[1] - eigs(B)[1]) 0.0 1e-8 - @test !issymmetric(B₁) - @test eltype(B₁) == Float64 + for g in testgraphs(pg) + nbt = Nonbacktracking(g) + B, emap = non_backtracking_matrix(g) + Bs = sparse(nbt) + @test sparse(B) == Bs + @test eigs(nbt, nev=1)[1] ≈ eigs(B, nev=1)[1] atol=1e-5 + + # check that matvec works + x = ones(Float64, nbt.m) + y = nbt * x + z = B * x + @test norm(y-z) < 1e-8 + + #check that matmat works and full(nbt) == B + @test norm(nbt*eye(nbt.m) - B) < 1e-8 + + #check that matmat works and full(nbt) == B + @test norm(nbt*eye(nbt.m) - B) < 1e-8 + + #check that we can use the implicit matvec in nonbacktrack_embedding + @test size(y) == size(x) + + B₁ = Nonbacktracking(g10) + + @test full(B₁) == full(B) + @test B₁ * ones(size(B₁)[2]) == B*ones(size(B)[2]) + @test size(B₁) == size(B) + # @test norm(eigs(B₁)[1] - eigs(B)[1]) ≈ 0.0 atol=1e-8 + @test !issymmetric(B₁) + @test eltype(B₁) == Float64 + end # END tests for Nonbacktracking # spectral distance checks for n=3:10 polygon = random_regular_graph(n, 2) - @test isapprox(spectral_distance(polygon, polygon), 0, atol=1e-8) - @test isapprox(spectral_distance(polygon, polygon, 1), 0, atol=1e-8) + for g in testgraphs(polygon) + @test isapprox(spectral_distance(g, g), 0, atol=1e-8) + @test isapprox(spectral_distance(g, g, 1), 0, atol=1e-8) + end end end diff --git a/test/operators.jl b/test/operators.jl index 4cf56116d..c193bd845 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -1,116 +1,134 @@ @testset "Operators" begin - c3 = complement(g3) - c4 = complement(g4) - - @test nv(c3) == 5 - @test ne(c3) == 6 - @test nv(c4) == 5 - @test ne(c4) == 16 - - g = reverse(g4) - @test re1 in edges(g) - reverse!(g) - @test g == g4 - - g = blkdiag(g3, g3) - @test nv(g) == 10 - @test ne(g) == 8 - - h = PathGraph(2) - @test intersect(g3, h) == h - - h = PathGraph(4) - z = difference(g3, h) - @test nv(z) == 5 - @test ne(z) == 1 - z = difference(h, g3) - @test nv(z) == 4 - @test ne(z) == 0 - - z = symmetric_difference(h,g3) - @test z == symmetric_difference(g3,h) - @test nv(z) == 5 - @test ne(z) == 1 - - h = Graph(6) - add_edge!(h, 5, 6) - e = Edge(5, 6) - z = union(g3, h) - @test has_edge(z, e) - @test z == PathGraph(6) - - h = DiGraph(6) - add_edge!(h, 5, 6) - e = Edge(5, 6) - z = union(g4, h) - @test has_edge(z, e) - @test z == PathDiGraph(6) - - g10 = CompleteGraph(2) - h10 = CompleteGraph(2) - z = blkdiag(g10, h10) - @test nv(z) == nv(g10) + nv(h10) - @test ne(z) == ne(g10) + ne(h10) - @test has_edge(z, 1, 2) - @test has_edge(z, 3, 4) - @test !has_edge(z, 1, 3) - @test !has_edge(z, 1, 4) - @test !has_edge(z, 2, 3) - @test !has_edge(z, 2, 4) - - g10 = Graph(2) - h10 = Graph(2) - z = join(g10, h10) - @test nv(z) == nv(g10) + nv(h10) - @test ne(z) == 4 - @test !has_edge(z, 1, 2) - @test !has_edge(z, 3, 4) - @test has_edge(z, 1, 3) - @test has_edge(z, 1, 4) - @test has_edge(z, 2, 3) - @test has_edge(z, 2, 4) - - p = PathGraph(10) - x = p*ones(10) - @test x[1]==1.0 && all(x[2:end-1].==2.0) && x[end]==1.0 - - @test size(p) == (10,10) - @test size(p, 1) == size(p, 2) == 10 - @test size(p, 3) == 1 - - - g5 = DiGraph(4) - add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) - @test g5 * ones(nv(g5)) == [2.0, 1.0, 1.0, 0.0] - @test sum(g5, 1) == [0, 1, 2, 1] - @test sum(g5, 2) == [2, 1, 1, 0] - @test sum(g5) == 4 - @test sum(p,1) == sum(p,2) - @test_throws ErrorException sum(p,3) - - @test sparse(p) == adjacency_matrix(p) - @test eltype(p) == Float64 - @test length(p) == 100 - @test ndims(p) == 2 - @test issymmetric(p) - @test !issymmetric(g5) - - g22 = CompleteGraph(2) - h = cartesian_product(g22, g22) - @test nv(h) == 4 - @test ne(h)== 4 - - g22 = CompleteGraph(2) - h = tensor_product(g22, g22) - @test nv(h) == 4 - @test ne(h) == 1 + g3 = PathGraph(5) + g4 = PathDiGraph(5) + + for g in testlargegraphs(g3) + T = eltype(g) + c = @inferred(complement(g)) + @test nv(c) == 5 + @test ne(c) == 6 + + gb = @inferred(blkdiag(g, g)) + @test nv(gb) == 10 + @test ne(gb) == 8 + + hp = PathGraph(2) + h = Graph{T}(hp) + @test @inferred(intersect(g, h)) == h + + hp = PathGraph(4) + h = Graph{T}(hp) + + z = @inferred(difference(g, h)) + @test nv(z) == 5 + @test ne(z) == 1 + z = @inferred(difference(h, g)) + @test nv(z) == 4 + @test ne(z) == 0 + z = @inferred(symmetric_difference(h,g)) + @test z == symmetric_difference(g,h) + @test nv(z) == 5 + @test ne(z) == 1 + + h = Graph{T}(6) + add_edge!(h, 5, 6) + e = SimpleEdge(5, 6) + + z = @inferred(union(g, h)) + @test has_edge(z, e) + @test z == PathGraph(6) + end + for g in testlargedigraphs(g4) + T = eltype(g) + c = @inferred(complement(g)) + @test nv(c) == 5 + @test ne(c) == 16 + + h = DiGraph{T}(6) + add_edge!(h, 5, 6) + e = SimpleEdge(5, 6) + + z = @inferred(union(g, h)) + @test has_edge(z, e) + @test z == PathDiGraph(6) + end - nx = 20; ny = 21 - G = PathGraph(ny); H = PathGraph(nx) - c = cartesian_product(G, H) - g = crosspath(ny, PathGraph(nx)); - @test g == c + re1 = Edge(2, 1) + gr = @inferred(reverse(g4)) + for g in testdigraphs(gr) + T = eltype(g) + @test re1 in edges(g) + @inferred(reverse!(g)) + @test g == DiGraph{T}(g4) + end + + gx = CompleteGraph(2) + + for g in testlargegraphs(gx) + T = eltype(g) + hc = CompleteGraph(2) + h = Graph{T}(hc) + z = @inferred(blkdiag(g, h)) + @test nv(z) == nv(g) + nv(h) + @test ne(z) == ne(g) + ne(h) + @test has_edge(z, 1, 2) + @test has_edge(z, 3, 4) + @test !has_edge(z, 1, 3) + @test !has_edge(z, 1, 4) + @test !has_edge(z, 2, 3) + @test !has_edge(z, 2, 4) + end + + gx = Graph(2) + for g in testgraphs(gx) + T = eltype(g) + h = Graph{T}(2) + z = @inferred(join(g, h)) + @test nv(z) == nv(g) + nv(h) + @test ne(z) == 4 + @test !has_edge(z, 1, 2) + @test !has_edge(z, 3, 4) + @test has_edge(z, 1, 3) + @test has_edge(z, 1, 4) + @test has_edge(z, 2, 3) + @test has_edge(z, 2, 4) + end + + px = PathGraph(10) + for p in testgraphs(px) + x = @inferred(p*ones(10)) + @test x[1] ==1.0 && all(x[2:end-1].==2.0) && x[end]==1.0 + @test size(p) == (10,10) + @test size(p, 1) == size(p, 2) == 10 + @test size(p, 3) == 1 + @test sum(p,1) == sum(p,2) + @test_throws ErrorException sum(p,3) + @test sparse(p) == adjacency_matrix(p) + @test length(p) == 100 + @test ndims(p) == 2 + @test issymmetric(p) + end + + gx = DiGraph(4) + add_edge!(gx,1,2); add_edge!(gx,2,3); add_edge!(gx,1,3); add_edge!(gx,3,4) + for g in testdigraphs(gx) + @test @inferred(g * ones(nv(g))) == [2.0, 1.0, 1.0, 0.0] + @test sum(g, 1) == [0, 1, 2, 1] + @test sum(g, 2) == [2, 1, 1, 0] + @test sum(g) == 4 + @test @inferred(!issymmetric(g)) + end + nx = 20; ny = 21 + gx = PathGraph(ny) + for g in testlargegraphs(gx) + T = eltype(g) + hp = PathGraph(nx) + h = Graph{T}(hp) + c = @inferred(cartesian_product(g, h)) + gz = @inferred(crosspath(ny, PathGraph(nx))) + @test gz == c + end function crosspath_slow(len, h) g = h m = nv(h) @@ -123,57 +141,79 @@ end return g end - @test crosspath_slow(2, g22) == crosspath(2,g22) + gx = CompleteGraph(2) + for g in testgraphs(gx) + h = @inferred(cartesian_product(g, g)) + @test nv(h) == 4 + @test ne(h) == 4 + + h = @inferred(tensor_product(g, g)) + @test nv(h) == 4 + @test ne(h) == 1 + end + g2 = CompleteGraph(2) + for g in testgraphs(g2) + @test crosspath_slow(2, g) == crosspath(2, g) + end ## test subgraphs ## - g = smallgraph(:bull) - n = 3 - h = g[1:n] - @test nv(h) == n - @test ne(h) == 3 - - h = g[[1,2,4]] - @test nv(h) == n - @test ne(h) == 2 - - h = g[[1,5]] - @test nv(h) == 2 - @test ne(h) == 0 - @test typeof(h) == typeof(g) - - g = DiGraph(100,200) - h = g[5:26] - @test nv(h) == 22 - @test typeof(h) == typeof(g) - @test_throws ErrorException g[[1,1]] - - r = 5:26 - h2, vm = induced_subgraph(g, r) - @test h2 == h - @test vm == collect(r) - @test h2 == g[r] - - sg, vm = induced_subgraph(CompleteGraph(10), 5:8) - @test nv(sg) == 4 - @test ne(sg) == 6 - - sg2, vm = induced_subgraph(CompleteGraph(10), [5,6,7,8]) - @test sg2 == sg - @test vm[4] == 8 - - gg5 = CompleteGraph(10) - elist = [Edge(1,2),Edge(2,3),Edge(3,4),Edge(4,5),Edge(5,1)] - sg, vm = induced_subgraph(gg5, elist) - @test sg == CycleGraph(5) - @test sort(vm) == [1:5;] - - - g10 = StarGraph(10) - @test egonet(g10, 1, 0) == Graph(1,0) - @test egonet(g10, 1, 1) == g10 - - @test eltype(g10) == Float64 - @test ndims(g10) == 2 + gb = smallgraph(:bull) + for g in testgraphs(gb) + n = 3 + h = @inferred(g[1:n]) + @test nv(h) == n + @test ne(h) == 3 + + h = @inferred(g[[1,2,4]]) + @test nv(h) == n + @test ne(h) == 2 + + h = @inferred(g[[1,5]]) + @test nv(h) == 2 + @test ne(h) == 0 + @test typeof(h) == typeof(g) + end + + gx = DiGraph(100,200) + for g in testdigraphs(gx) + h = @inferred(g[5:26]) + @test nv(h) == 22 + @test typeof(h) == typeof(g) + @test_throws ErrorException g[[1,1]] + + r = 5:26 + h2, vm = @inferred(induced_subgraph(g, r)) + @test h2 == h + @test vm == collect(r) + @test h2 == g[r] + end + + g10 = CompleteGraph(10) + for g in testgraphs(g10) + sg, vm = @inferred(induced_subgraph(g, 5:8)) + @test nv(sg) == 4 + @test ne(sg) == 6 + + sg2, vm = @inferred(induced_subgraph(g, [5,6,7,8])) + @test sg2 == sg + @test vm[4] == 8 + + elist = [ + SimpleEdge(1, 2), SimpleEdge(2, 3), SimpleEdge(3, 4), + SimpleEdge(4, 5),SimpleEdge(5, 1) + ] + sg, vm = @inferred(induced_subgraph(g, elist)) + @test sg == CycleGraph(5) + @test sort(vm) == [1:5;] + end + + gs = StarGraph(10) + for g in testgraphs(gs) + T = eltype(g) + @test @inferred(egonet(g, 1, 0)) == Graph{T}(1) + @test @inferred(egonet(g, 1, 1)) == g + @test @inferred(ndims(g)) == 2 + end end diff --git a/test/persistence/persistence.jl b/test/persistence/persistence.jl index 7f72b34ee..cd60bc7c0 100644 --- a/test/persistence/persistence.jl +++ b/test/persistence/persistence.jl @@ -1,4 +1,9 @@ @testset "Persistence" begin + pdict = load(joinpath(testdir,"testdata","tutte-pathdigraph.jgz")) + p1 = pdict["Tutte"] + p2 = pdict["pathdigraph"] + g3 = PathGraph(5) + function readback_test(format::Symbol, g::Graph, gname="g", remove=true, fnamefio=mktemp()) fname,fio = fnamefio diff --git a/test/runtests.jl b/test/runtests.jl index 4db9e3e86..83b3b8a7e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,63 +1,61 @@ using LightGraphs +using LightGraphs.SimpleGraphs using LightGraphs.LinAlg using Base.Test -g1 = smallgraph(:petersen) -g2 = smallgraph(:tutte) -g3 = PathGraph(5) -g4 = PathDiGraph(5) -g5 = DiGraph(4) -add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) -g6 = smallgraph(:house) - -h1 = Graph(5) -h2 = Graph(3) -h3 = Graph() -h4 = DiGraph(7) -h5 = DiGraph() - -# self loops -s2 = DiGraph(3) -add_edge!(s2,1,2); add_edge!(s2,2,3); add_edge!(s2,3,3) -s1 = Graph(s2) - -r1 = Graph(10,20) -r2 = DiGraph(5,10) - -e0 = Edge(2, 3) -e1 = Edge(1, 2) -re1 = Edge(2, 1) - -# polygons -triangle = random_regular_graph(3,2) -quadrangle = random_regular_graph(4,2) -pentagon = random_regular_graph(5,2) +# g1 = smallgraph(:petersen) +# g2 = smallgraph(:tutte) +# g3 = PathGraph(5) +# g4 = PathDiGraph(5) +# g5 = DiGraph(4) +# add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) +# g6 = smallgraph(:house) +# +# h1 = Graph(5) +# h2 = Graph(3) +# h3 = Graph() +# h4 = DiGraph(7) +# h5 = DiGraph() +# +# # self loops +# s2 = DiGraph(3) +# add_edge!(s2,1,2); add_edge!(s2,2,3); add_edge!(s2,3,3) +# s1 = Graph(s2) +# +# r1 = Graph(10,20) +# r2 = DiGraph(5,10) +# +# e0 = Edge(2, 3) +# e1 = Edge(1, 2) +# re1 = Edge(2, 1) +# +# # polygons +# triangle = random_regular_graph(3,2) +# quadrangle = random_regular_graph(4,2) +# pentagon = random_regular_graph(5,2) testdir = dirname(@__FILE__) -pdict = load(joinpath(testdir,"testdata","tutte-pathdigraph.jgz")) -p1 = pdict["Tutte"] -p2 = pdict["pathdigraph"] - - -adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph -adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph -distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] -distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] +testgraphs(g) = [g, Graph{UInt8}(g), Graph{Int16}(g)] +testdigraphs(g) = [g, DiGraph{UInt8}(g), DiGraph{Int16}(g)] -a1 = Graph(adjmx1) -a2 = DiGraph(adjmx2) +# some operations will create a large graph from two smaller graphs. We +# might error out on very small eltypes. +testlargegraphs(g) = [g, Graph{UInt16}(g), Graph{Int32}(g)] +testlargedigraphs(g) = [g, DiGraph{UInt16}(g), DiGraph{Int32}(g)] tests = [ + "interface", "core", - "edgeiter", + "graphtypes/simplegraphs/simplegraphs", + "graphtypes/simplegraphs/simpleedge", + "graphtypes/simplegraphs/simpleedgeiter", "operators", - "graphdigraph", + # "graphdigraph", "distance", "edit_distance", "linalg/spectral", "linalg/graphmatrices", - "cliques", "connectivity", "transitivity", "persistence/persistence", @@ -74,6 +72,7 @@ tests = [ "traversals/maxadjvisit", "traversals/graphvisit", "traversals/randomwalks", + "community/cliques", "community/core-periphery", "community/label_propagation", "community/modularity", diff --git a/test/shortestpaths/astar.jl b/test/shortestpaths/astar.jl index 7f7d5e173..014fd9773 100644 --- a/test/shortestpaths/astar.jl +++ b/test/shortestpaths/astar.jl @@ -1,8 +1,13 @@ @testset "A star" begin + g3 = PathGraph(5) + g4 = PathDiGraph(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]) 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])) - @test a_star(g3, 1, 4, d1) == - a_star(g4, 1, 4, d1) == - a_star(g3, 1, 4, d2) - @test a_star(g4, 4, 1) == nothing + for g in testgraphs(g3), dg in testdigraphs(g4) + @test @inferred(a_star(g, 1, 4, d1)) == + @inferred(a_star(dg, 1, 4, d1)) == + @inferred(a_star(g, 1, 4, d2)) + @test isempty(@inferred(a_star(dg, 4, 1))) + end end diff --git a/test/shortestpaths/bellman-ford.jl b/test/shortestpaths/bellman-ford.jl index 0db6aaaa0..cdf92dc21 100644 --- a/test/shortestpaths/bellman-ford.jl +++ b/test/shortestpaths/bellman-ford.jl @@ -1,29 +1,45 @@ @testset "Bellman Ford" begin + g4 = PathDiGraph(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]) 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])) + for g in testdigraphs(g4) + y = @inferred(bellman_ford_shortest_paths(g, 2, d1)) + z = @inferred(bellman_ford_shortest_paths(g, 2, d2)) + @test y.dists == z.dists == [Inf, 0, 6, 17, 33] + @test @inferred(enumerate_paths(z))[2] == [] + @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z,4) == [2,3,4] + @test @inferred(!has_negative_edge_cycle(g)) + @test @inferred(!has_negative_edge_cycle(g, d1)) - y = bellman_ford_shortest_paths(g4, 2, d1) - z = bellman_ford_shortest_paths(g4, 2, d2) - @test y.dists == z.dists == [Inf, 0, 6, 17, 33] - @test enumerate_paths(z)[2] == [] - @test enumerate_paths(z)[4] == enumerate_paths(z,4) == [2,3,4] - @test !has_negative_edge_cycle(g4) - z = bellman_ford_shortest_paths(g4, 2) - @test z.dists == [typemax(Int), 0, 1, 2, 3] + + y = @inferred(bellman_ford_shortest_paths(g, 2, d1)) + z = @inferred(bellman_ford_shortest_paths(g, 2, d2)) + @test y.dists == z.dists == [Inf, 0, 6, 17, 33] + @test @inferred(enumerate_paths(z))[2] == [] + @test @inferred(enumerate_paths(z))[4] == enumerate_paths(z,4) == [2,3,4] + @test @inferred(!has_negative_edge_cycle(g)) + z = @inferred(bellman_ford_shortest_paths(g, 2)) + @test z.dists == [typemax(Int), 0, 1, 2, 3] + end # Negative Cycle gx = CompleteGraph(3) - d = [1 -3 1; -3 1 1; 1 1 1] - @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(gx, 1, d) - - # Negative Cycle - gx = CompleteGraph(3) - d = [1 -1 1; -1 1 1; 1 1 1] - @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(gx, 1, d) - + for g in testgraphs(gx) + d = [1 -3 1; -3 1 1; 1 1 1] + @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) + @test has_negative_edge_cycle(g, d) + + d = [1 -1 1; -1 1 1; 1 1 1] + @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) + @test has_negative_edge_cycle(g, d) + end + # Negative cycle of length 3 in graph of diameter 4 gx = CompleteGraph(4) d = [1 -1 1 1; 1 1 1 -1; 1 1 1 1; 1 1 1 1] - @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(gx, 1, d) - + for g in testgraphs(gx) + @test_throws LightGraphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) + @test has_negative_edge_cycle(g, d) + end end diff --git a/test/shortestpaths/dijkstra.jl b/test/shortestpaths/dijkstra.jl index ed27504fa..dcb480d7c 100644 --- a/test/shortestpaths/dijkstra.jl +++ b/test/shortestpaths/dijkstra.jl @@ -1,34 +1,38 @@ @testset "Dijkstra" begin + g4 = PathDiGraph(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]) 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])) - y = dijkstra_shortest_paths(g4, 2, d1) - z = dijkstra_shortest_paths(g4, 2, d2) + for g in testdigraphs(g4) + y = @inferred(dijkstra_shortest_paths(g, 2, d1)) + z = @inferred(dijkstra_shortest_paths(g, 2, d2)) - @test y.parents == z.parents == [0, 0, 2, 3, 4] - @test y.dists == z.dists == [Inf, 0, 6, 17, 33] + @test y.parents == z.parents == [0, 0, 2, 3, 4] + @test y.dists == z.dists == [Inf, 0, 6, 17, 33] - y = dijkstra_shortest_paths(g4, 2, d1; allpaths=true) - z = dijkstra_shortest_paths(g4, 2, d2; allpaths=true) - @test z.predecessors[3] == y.predecessors[3] == [2] + y = @inferred(dijkstra_shortest_paths(g, 2, d1; allpaths=true)) + z = @inferred(dijkstra_shortest_paths(g, 2, d2; allpaths=true)) + @test z.predecessors[3] == y.predecessors[3] == [2] - @test enumerate_paths(z) == enumerate_paths(y) - @test enumerate_paths(z)[4] == - enumerate_paths(z,4) == - enumerate_paths(y,4) == [2,3,4] + @test @inferred(enumerate_paths(z)) == enumerate_paths(y) + @test @inferred(enumerate_paths(z))[4] == + enumerate_paths(z,4) == + enumerate_paths(y,4) == [2,3,4] + end - g = PathGraph(5) - add_edge!(g,2,4) + gx = PathGraph(5) + add_edge!(gx,2,4) d = ones(Int, 5,5) d[2,3] = 100 - z = dijkstra_shortest_paths(g,1,d) - @test z.dists == [0, 1, 3, 2, 3] - @test z.parents == [0, 1, 4, 2, 4] - + for g in testgraphs(gx) + z = @inferred(dijkstra_shortest_paths(g,1,d)) + @test z.dists == [0, 1, 3, 2, 3] + @test z.parents == [0, 1, 4, 2, 4] + end # small function to reconstruct the shortest path; I copied it from somewhere, can't find the original source to give the credits # @Beatzekatze on github - spath(target, dijkstraStruct, sourse) = target == sourse ? target : [spath(dijkstraStruct.parents[target], dijkstraStruct, sourse) target] + spath(target, dijkstraStruct, source) = target == source ? target : [spath(dijkstraStruct.parents[target], dijkstraStruct, source) target] function spaths(ds, targets, source) shortest_paths = [] for i in targets @@ -49,18 +53,21 @@ 3. 0. 2. 0.; 0. 2. 0. 3.; 1. 0. 3. 0.] - ds = dijkstra_shortest_paths(G,2,w) - # this loop reconstructs the shortest path for nodes 1, 3 and 4 - @test spaths(ds, [1,3,4], 2) == Array[[2 1], - [2 3], - [2 1 4]] + + for g in testgraphs(G) + ds = @inferred(dijkstra_shortest_paths(g,2,w)) + # this loop reconstructs the shortest path for vertices 1, 3 and 4 + @test spaths(ds, [1,3,4], 2) == Array[[2 1], + [2 3], + [2 1 4]] # here a selflink at source is introduced; it should not change the shortest paths - w[2,2] = 10.0 - ds = dijkstra_shortest_paths(G,2,w) - shortest_paths = [] - # this loop reconstructs the shortest path for nodes 1, 3 and 4 - @test spaths(ds, [1,3,4], 2) == Array[[2 1], - [2 3], - [2 1 4]] + w[2,2] = 10.0 + ds = @inferred(dijkstra_shortest_paths(g,2,w)) + shortest_paths = [] + # this loop reconstructs the shortest path for vertices 1, 3 and 4 + @test spaths(ds, [1,3,4], 2) == Array[[2 1], + [2 3], + [2 1 4]] + end end diff --git a/test/shortestpaths/floyd-warshall.jl b/test/shortestpaths/floyd-warshall.jl index b48f77866..0d01d2761 100644 --- a/test/shortestpaths/floyd-warshall.jl +++ b/test/shortestpaths/floyd-warshall.jl @@ -1,9 +1,12 @@ @testset "Floyd Warshall" begin + g3 = PathGraph(5) 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] - z = floyd_warshall_shortest_paths(g3, d) - @test z.dists[3,:][:] == [7, 6, 0, 11, 27] - @test z.parents[3,:][:] == [2, 3, 0, 3, 4] + for g in testgraphs(g3) + z = @inferred(floyd_warshall_shortest_paths(g, d)) + @test z.dists[3,:][:] == [7, 6, 0, 11, 27] + @test z.parents[3,:][:] == [2, 3, 0, 3, 4] - @test enumerate_paths(z)[2][2] == [] - @test enumerate_paths(z)[2][4] == enumerate_paths(z,2)[4] == enumerate_paths(z,2,4) == [2,3,4] + @test @inferred(enumerate_paths(z))[2][2] == [] + @test @inferred(enumerate_paths(z))[2][4] == enumerate_paths(z,2)[4] == enumerate_paths(z,2,4) == [2,3,4] + end end diff --git a/test/spanningtrees/kruskal.jl b/test/spanningtrees/kruskal.jl index e35be3b1d..7c134290a 100644 --- a/test/spanningtrees/kruskal.jl +++ b/test/spanningtrees/kruskal.jl @@ -1,5 +1,5 @@ @testset "Kruskal" begin - g = CompleteGraph(4) + g4 = CompleteGraph(4) distmx = [ 0 1 5 6 @@ -8,14 +8,14 @@ 6 10 3 0 ] - # Testing Kruskal's algorithm - mst = kruskal_mst(g, distmx) vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) - - @test mst == vec_mst - - #second test - distmx_sec = [ + for g in testgraphs(g4) + # Testing Kruskal's algorithm + mst = @inferred(kruskal_mst(g, distmx)) + @test mst == vec_mst + end + #second test + distmx_sec = [ 0 0 0.26 0 0.38 0 0.58 0.16 0 0 0.36 0.29 0 0.32 0 0.19 0.26 0.36 0 0.17 0 0 0.4 0.34 @@ -26,9 +26,10 @@ 0.16 0.19 0.34 0 0.37 0.28 0 0 ] - g = Graph(distmx_sec) - mst2 = kruskal_mst(g, distmx_sec) + gx = Graph(distmx_sec) vec2 = Vector{Edge}([Edge(1, 8),Edge(3, 4),Edge(2, 8),Edge(1, 3),Edge(6, 8),Edge(5, 6),Edge(3, 7)]) - - @test mst2 == vec2 + for g in testgraphs(gx) + mst2 = @inferred(kruskal_mst(g, distmx_sec)) + @test mst2 == vec2 + end end diff --git a/test/spanningtrees/prim.jl b/test/spanningtrees/prim.jl index 00c506c23..5363eb529 100644 --- a/test/spanningtrees/prim.jl +++ b/test/spanningtrees/prim.jl @@ -1,5 +1,5 @@ @testset "Prim" begin - g = CompleteGraph(4) + g4 = CompleteGraph(4) distmx = [ 0 1 5 6 @@ -8,11 +8,12 @@ 6 10 3 0 ] - # Testing Prim's algorithm - mst = prim_mst(g, distmx) vec_mst = Vector{Edge}([Edge(1, 2), Edge(2, 3), Edge(3, 4)]) - - @test mst == vec_mst + for g in testgraphs(g4) + # Testing Prim's algorithm + mst = @inferred(prim_mst(g, distmx)) + @test mst == vec_mst + end #second test distmx_sec = [ @@ -26,9 +27,10 @@ 0.16 0.19 0.34 0 0.37 0.28 0 0 ] - g = Graph(distmx_sec) - mst2 = prim_mst(g, distmx_sec) vec2 = Vector{Edge}([Edge(1, 8),Edge(2, 8),Edge(1, 3),Edge(3, 4),Edge(6, 8),Edge(5, 6),Edge(3, 7)]) - - @test mst2 == vec2 + gx = Graph(distmx_sec) + for g in testgraphs(gx) + mst2 = @inferred(prim_mst(g, distmx_sec)) + @test mst2 == vec2 + end end diff --git a/test/transitivity.jl b/test/transitivity.jl index 4663c7ef7..af9eb2d4a 100644 --- a/test/transitivity.jl +++ b/test/transitivity.jl @@ -1,22 +1,30 @@ @testset "Transitivity" begin - complete = CompleteDiGraph(4) - circle = PathDiGraph(4) - add_edge!(circle, 4, 1) - newcircle = transitiveclosure(circle) - @test newcircle == complete - @test ne(circle) == 4 - @test newcircle == transitiveclosure!(circle) - @test ne(circle) == 12 + completedg = CompleteDiGraph(4) + circledg = PathDiGraph(4) + add_edge!(circledg, 4, 1) + for circle in testdigraphs(circledg) + T = eltype(circle) + complete = DiGraph{T}(completedg) + newcircle = @inferred(transitiveclosure(circle)) + @test newcircle == complete + @test ne(circle) == 4 + @test newcircle == @inferred(transitiveclosure!(circle)) + @test ne(circle) == 12 + end - loopedcomplete = copy(complete) - for i in vertices(loopedcomplete) - add_edge!(loopedcomplete, i, i) + loopedcompletedg = copy(completedg) + for i in vertices(loopedcompletedg) + add_edge!(loopedcompletedg, i, i) + end + circledg = PathDiGraph(4) + add_edge!(circledg, 4, 1) + for circle in testdigraphs(circledg) + T = eltype(circle) + loopedcomplete = DiGraph{T}(loopedcompletedg) + newcircle = @inferred(transitiveclosure(circle, true)) + @test newcircle == loopedcomplete + @test ne(circle) == 4 + @test newcircle == @inferred(transitiveclosure!(circle, true)) + @test ne(circle) == 16 end - circle = PathDiGraph(4) - add_edge!(circle, 4, 1) - newcircle = transitiveclosure(circle, true) - @test newcircle == loopedcomplete - @test ne(circle) == 4 - @test newcircle == transitiveclosure!(circle, true) - @test ne(circle) == 16 end diff --git a/test/traversals/bfs.jl b/test/traversals/bfs.jl index 0f681bf8e..198ca2f78 100644 --- a/test/traversals/bfs.jl +++ b/test/traversals/bfs.jl @@ -1,33 +1,41 @@ +import LightGraphs: TreeBFSVisitorVector, bfs_tree!, tree @testset "BFS" begin g5 = DiGraph(4) add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) + g6 = smallgraph(:house) - z = bfs_tree(g5, 1) - visitor = LightGraphs.TreeBFSVisitorVector(zeros(Int,nv(g5))) - LightGraphs.bfs_tree!(visitor, g5, 1) - t = visitor.tree - @test t == [1,1,1,3] - @test nv(z) == 4 && ne(z) == 3 && !has_edge(z, 2, 3) + for g in testdigraphs(g5) + z = @inferred(bfs_tree(g, 1)) + visitor = LightGraphs.TreeBFSVisitorVector(zeros(eltype(g),nv(g))) + LightGraphs.bfs_tree!(visitor, g, 1) + t = visitor.tree + @test t == [1,1,1,3] + @test nv(z) == 4 && ne(z) == 3 && !has_edge(z, 2, 3) + end + for g in testgraphs(g6) + @test @inferred(gdistances(g, 2)) == [1, 0, 2, 1, 2] + @test @inferred(gdistances(g, [1,2])) == [0, 0, 1, 1, 2] + @test @inferred(gdistances(g, [])) == [-1, -1, -1, -1, -1] + @test @inferred(!is_bipartite(g)) + @test @inferred(!is_bipartite(g, 2)) + end - @test gdistances(g6, 2) == [1, 0, 2, 1, 2] - @test gdistances(g6, [1,2]) == [0, 0, 1, 1, 2] - @test gdistances(g6, []) == [-1, -1, -1, -1, -1] - @test !is_bipartite(g6) - @test !is_bipartite(g6, 2) + gx = Graph(5) + add_edge!(gx,1,2); add_edge!(gx,1,4) + add_edge!(gx,2,3); add_edge!(gx,2,5) + add_edge!(gx,3,4) - g = Graph(5) - add_edge!(g,1,2); add_edge!(g,1,4) - add_edge!(g,2,3); add_edge!(g,2,5) - add_edge!(g,3,4) - @test is_bipartite(g) - @test is_bipartite(g, 2) + for g in testgraphs(gx) + @test @inferred(is_bipartite(g)) + @test @inferred(is_bipartite(g, 2)) + end - import LightGraphs: TreeBFSVisitorVector, bfs_tree!, tree + # import LightGraphs: TreeBFSVisitorVector, bfs_tree!, tree - function istree(parents::Vector{Int}, maxdepth) + function istree{T<:Integer}(parents::Vector{T}, maxdepth, n::T) flag = true - for i in 1:n + for i in one(T):n s = i depth = 0 while parents[s] > 0 && parents[s] != s @@ -41,35 +49,44 @@ return flag end - n = nv(g6) - visitor = TreeBFSVisitorVector(n) - @test length(visitor.tree) == n - parents = visitor.tree - bfs_tree!(visitor, g6, 1) + for g in testgraphs(g6) + n = nv(g) + visitor = @inferred(TreeBFSVisitorVector(n)) + @test length(visitor.tree) == n + parents = visitor.tree + @inferred(bfs_tree!(visitor, g, 1)) - @test istree(parents, n) == true - t = tree(parents) - @test typeof(t) <: DiGraph - @test ne(t) < nv(t) + @test istree(parents, n, n) + t = tree(parents) + @test is_directed(t) + @test typeof(t) <: AbstractGraph + @test ne(t) < nv(t) # test Dict{Int,Int}() colormap - n = nv(g6) - visitor = TreeBFSVisitorVector(n) - @test length(visitor.tree) == n - parents = visitor.tree - bfs_tree!(visitor, g6, 1, vertexcolormap = Dict{Int,Int}()) - @test istree(parents, n) == true - t = tree(parents) - @test typeof(t) <: DiGraph - @test ne(t) < nv(t) + visitor = @inferred(TreeBFSVisitorVector(n)) + @test length(visitor.tree) == n + parents = visitor.tree + @inferred(bfs_tree!(visitor, g, 1, vertexcolormap = Dict{Int,Int}())) + + @test @inferred(istree(parents, n, n)) + t = tree(parents) + @test is_directed(t) + @test typeof(t) <: AbstractGraph + @test ne(t) < nv(t) + end g10 = CompleteGraph(10) - @test bipartite_map(g10) == Vector{Int}() + for g in testgraphs(g10) + @test @inferred(bipartite_map(g)) == Vector{eltype(g)}() + end g10 = CompleteBipartiteGraph(10,10) - @test bipartite_map(g10) == Vector{Int}([ones(10); 2*ones(10)]) + for g in testgraphs(g10) + T = eltype(g) + @test @inferred(bipartite_map(g10)) == Vector{T}([ones(T, 10); 2*ones(T, 10)]) - h10 = blkdiag(g10,g10) - @test bipartite_map(h10) == Vector{Int}([ones(10); 2*ones(10); ones(10); 2*ones(10)]) + h = blkdiag(g,g) + @test @inferred(bipartite_map(h)) == Vector{T}([ones(T, 10); 2*ones(T, 10); ones(T, 10); 2*ones(T, 10)]) + end end diff --git a/test/traversals/dfs.jl b/test/traversals/dfs.jl index 4b54aa32c..a3d684fb0 100644 --- a/test/traversals/dfs.jl +++ b/test/traversals/dfs.jl @@ -1,14 +1,17 @@ @testset "DFS" begin - z = dfs_tree(g5,1) + g5 = DiGraph(4) + add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4) + for g in testdigraphs(g5) + z = @inferred(dfs_tree(g,1)) + @test ne(z) == 3 && nv(z) == 4 + @test !has_edge(z, 1, 3) - @test ne(z) == 3 && nv(z) == 4 - @test !has_edge(z, 1, 3) + @test @inferred(topological_sort_by_dfs(g)) == [1, 2, 3, 4] + @test !is_cyclic(g) + end - @test topological_sort_by_dfs(g5) == [1, 2, 3, 4] - @test !is_cyclic(g5) - g = DiGraph(3) - - add_edge!(g,1,2); add_edge!(g,2,3); add_edge!(g,3,1) - - @test is_cyclic(g) + gx = CycleDiGraph(3) + for g in testdigraphs(gx) + @test @inferred(is_cyclic(g)) + end end diff --git a/test/traversals/graphvisit.jl b/test/traversals/graphvisit.jl index a8f1983c2..f658faa0d 100644 --- a/test/traversals/graphvisit.jl +++ b/test/traversals/graphvisit.jl @@ -1,30 +1,39 @@ @testset "Graph visit" begin - # stub tests for coverage; disregards output. + g6 = smallgraph(:house) + for g in testgraphs(g6) + # stub tests for coverage; disregards output. - f = IOBuffer() + f = IOBuffer() - @test traverse_graph_withlog(g6, BreadthFirst(), [1;], f) == nothing + @test @inferred(traverse_graph_withlog(g, BreadthFirst(), [1;], f)) == nothing + @test @inferred(visited_vertices(g, BreadthFirst(), [1;])) == [1, 2, 3, 4, 5] - @test visited_vertices(g6, BreadthFirst(), [1;]) == [1, 2, 3, 4, 5] + vis = TrivialGraphVisitor() + @test discover_vertex!(vis, 1) + @test open_vertex!(vis, 1) + @test examine_neighbor!(vis, 1, 1, 0, 0, 0) + @test close_vertex!(vis, 1) - function trivialgraphvisit( - g::AbstractGraph, - alg::LightGraphs.AbstractGraphVisitAlgorithm, - sources - ) - visitor = TrivialGraphVisitor() - traverse_graph!(g, alg, sources, visitor) - end - - @test trivialgraphvisit(g6, BreadthFirst(), 1) == nothing + function trivialgraphvisit( + g::AbstractGraph, + alg::LightGraphs.AbstractGraphVisitAlgorithm, + sources + ) + visitor = TrivialGraphVisitor() + @inferred(traverse_graph!(g, alg, sources, visitor)) + end - # this just exercises some graph visitors - @test traverse_graph!(g6, BreadthFirst(), 1, TrivialGraphVisitor()) == nothing - @test traverse_graph!(g6, BreadthFirst(), 1, LogGraphVisitor(IOBuffer())) == nothing + @test trivialgraphvisit(g, BreadthFirst(), 1) == nothing + # this just exercises some graph visitors + @test @inferred(traverse_graph!(g, BreadthFirst(), 1, TrivialGraphVisitor())) == nothing + @test @inferred(traverse_graph!(g, BreadthFirst(), 1, LogGraphVisitor(IOBuffer()))) == nothing + end # dummy edge map test - d = LightGraphs.DummyEdgeMap() + d = @inferred(LightGraphs.DummyEdgeMap()) + e = Edge(1,2) @test d[e] == 0 + @test getindex(d, e) == 0 end diff --git a/test/traversals/maxadjvisit.jl b/test/traversals/maxadjvisit.jl index 3084d375f..1d15f8863 100644 --- a/test/traversals/maxadjvisit.jl +++ b/test/traversals/maxadjvisit.jl @@ -1,6 +1,6 @@ @testset "Max adj visit" begin - g = Graph(8) + gx = Graph(8) # Test of Min-Cut and maximum adjacency visit # Original example by Stoer @@ -21,30 +21,30 @@ m = length(wedges) - eweights = spzeros(nv(g),nv(g)) + eweights = spzeros(nv(gx),nv(gx)) for (s, d, w) in wedges - add_edge!(g, s, d) + add_edge!(gx, s, d) eweights[s, d] = w eweights[d, s] = w end + for g in testgraphs(gx) + @test nv(g) == 8 + @test ne(g) == m - @test nv(g) == 8 - @test ne(g) == m + parity, bestcut = @inferred(mincut(g, eweights)) - parity, bestcut = mincut(g, eweights) + @test length(parity) == 8 + @test parity == [2, 2, 1, 1, 2, 2, 1, 1] + @test bestcut == 4.0 - @test length(parity) == 8 - @test parity == [2, 2, 1, 1, 2, 2, 1, 1] - @test bestcut == 4.0 + parity, bestcut = @inferred(mincut(g)) - parity, bestcut = mincut(g) + @test length(parity) == 8 + @test parity == [2, 1, 1, 1, 1, 1, 1, 1] + @test bestcut == 2.0 - @test length(parity) == 8 - @test parity == [2, 1, 1, 1, 1, 1, 1, 1] - @test bestcut == 2.0 - - v = maximum_adjacency_visit(g) - - @test v == Vector{Int64}([1, 2, 5, 6, 3, 7, 4, 8]) + v = @inferred(maximum_adjacency_visit(g)) + @test v == Vector{Int64}([1, 2, 5, 6, 3, 7, 4, 8]) + end end diff --git a/test/traversals/randomwalks.jl b/test/traversals/randomwalks.jl index cebcb7868..9f372e3ee 100644 --- a/test/traversals/randomwalks.jl +++ b/test/traversals/randomwalks.jl @@ -19,7 +19,7 @@ Used only for testing and debugging. """ function test_nbw(g, start, len) - w = non_backtracking_randomwalk(g, start, len) + w = @inferred(non_backtracking_randomwalk(g, start, len)) if is_nonbacktracking(w) return true else @@ -27,50 +27,58 @@ end return false end - g = PathDiGraph(10) - @test randomwalk(g, 1, 5) == [1:5;] - @test randomwalk(g, 2, 100) == [2:10;] - @test_throws BoundsError randomwalk(g, 20, 20) - - g = PathGraph(10) - @test saw(g, 1, 20) == [1:10;] - @test_throws BoundsError saw(g, 20, 20) - - g = PathGraph(10) - @test non_backtracking_randomwalk(g, 1, 20) == [1:10;] - @test_throws BoundsError non_backtracking_randomwalk(g, 20, 20) - - g = DiGraph(PathGraph(10)) - @test non_backtracking_randomwalk(g, 1, 20) == [1:10;] - @test_throws BoundsError non_backtracking_randomwalk(g, 20, 20) + gx = PathDiGraph(10) + for g in testdigraphs(gx) + @test @inferred(randomwalk(g, 1, 5)) == [1:5;] + @test @inferred(randomwalk(g, 2, 100)) == [2:10;] + @test_throws BoundsError randomwalk(g, 20, 20) + @test @inferred(non_backtracking_randomwalk(g, 10, 20)) == [10] + @test @inferred(non_backtracking_randomwalk(g, 1, 20)) == [1:10;] + end - g = PathDiGraph(10) - @test non_backtracking_randomwalk(g, 10, 20) == [10] + gx = PathGraph(10) + for g in testgraphs(gx) + @test @inferred(saw(g, 1, 20)) == [1:10;] + @test_throws BoundsError saw(g, 20, 20) + @test @inferred(non_backtracking_randomwalk(g, 1, 20)) == [1:10;] + @test_throws BoundsError non_backtracking_randomwalk(g, 20, 20) + end - g = PathDiGraph(10) - @test non_backtracking_randomwalk(g, 1, 20) == [1:10;] + gx = DiGraph(PathGraph(10)) + for g in testdigraphs(gx) + @test @inferred(non_backtracking_randomwalk(g, 1, 20)) == [1:10;] + @test_throws BoundsError non_backtracking_randomwalk(g, 20, 20) + end - g = CycleGraph(10) - visited = non_backtracking_randomwalk(g, 1, 20) - @test visited == [1:10; 1:10;] || visited == [1; 10:-1:1; 10:-1:2;] + gx = CycleGraph(10) + for g in testgraphs(gx) + visited = @inferred(non_backtracking_randomwalk(g, 1, 20)) + @test visited == [1:10; 1:10;] || visited == [1; 10:-1:1; 10:-1:2;] + end - g = CycleDiGraph(10) - @test non_backtracking_randomwalk(g, 1, 20) == [1:10; 1:10;] + gx = CycleDiGraph(10) + for g in testdigraphs(gx) + @test @inferred(non_backtracking_randomwalk(g, 1, 20)) == [1:10; 1:10;] + end n = 10 - g = CycleGraph(n) + gx = CycleGraph(n) for k = 3:n-1 - add_edge!(g, 1, k) + add_edge!(gx, 1, k) end - for len = 1:3*n + for g in testgraphs(gx) + for len = 1:3*n @test test_nbw(g,1,len) @test test_nbw(g,2,len) + end end #test to make sure it works with self loops. - add_edge!(g, 1, 1) - for len = 1:3*n - @test test_nbw(g,1,len) == true - @test test_nbw(g,2,len) == true + add_edge!(gx, 1, 1) + for g in testgraphs(gx) + for len = 1:3*n + @test test_nbw(g,1,len) + @test test_nbw(g,2,len) + end end end diff --git a/test/utils.jl b/test/utils.jl index 283278207..ae8a4f117 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -1,17 +1,17 @@ @testset "Utils" begin - s = LightGraphs.sample!([1:10;], 3) + s = @inferred(LightGraphs.sample!([1:10;], 3)) @test length(s) == 3 for e in s @test 1 <= e <= 10 end - s = LightGraphs.sample!([1:10;], 6, exclude=[1,2]) + s = @inferred(LightGraphs.sample!([1:10;], 6, exclude=[1,2])) @test length(s) == 6 for e in s @test 3 <= e <= 10 end - s = LightGraphs.sample(1:10, 6, exclude=[1,2]) + s = @inferred(LightGraphs.sample(1:10, 6, exclude=[1,2])) @test length(s) == 6 for e in s @test 3 <= e <= 10