diff --git a/src/Graphs.jl b/src/Graphs.jl index 6d5ecf057..ab62d4811 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -24,11 +24,13 @@ import Base: adjoint, write, ==, <, *, ≈, convert, isless, issubset, union, in export # Interface -AbstractGraph, AbstractEdge, AbstractEdgeIter, +AbstractGraph, is_vertex, AbstractVertex, AbstractEdge, AbstractWeightedEdge, AbstractEdgeIter, Edge, Graph, SimpleGraph, SimpleGraphFromIterator, DiGraph, SimpleDiGraphFromIterator, SimpleDiGraph, vertices, edges, edgetype, nv, ne, src, dst, -is_directed, IsDirected, -has_vertex, has_edge, inneighbors, outneighbors, +is_directed, IsDirected, is_range_based, IsRangeBased, is_simply_mutable, IsSimplyMutable, +is_mutable, IsMutable, is_weight_mutable, IsWeightMutable, is_vertex_stable, IsVertexStable, +has_vertex, has_edge, inneighbors, outneighbors, outedges, inedges, +weight, get_vertex_container, get_edge_container, # core is_ordered, add_vertices!, indegree, outdegree, degree, diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index ecc870af3..d890001ee 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -10,8 +10,10 @@ import Base: import Graphs: _NI, AbstractGraph, AbstractEdge, AbstractEdgeIter, - src, dst, edgetype, nv, ne, vertices, edges, is_directed, + src, dst, edgetype, nv, ne, vertices, edges, outedges, inedges, is_directed, + is_simply_mutable, is_range_based, has_vertex, has_edge, inneighbors, outneighbors, all_neighbors, + get_vertex_container, get_edge_container, deepcopy_adjlist, indegree, outdegree, degree, has_self_loops, num_self_loops, insorted, squash @@ -48,7 +50,10 @@ An abstract type representing a simple graph structure. - `fadjlist::Vector{Vector{Integer}}` - `ne::Integer` """ -abstract type AbstractSimpleGraph{T<:Integer} <: AbstractGraph{T} end + +abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T, Int} end + +abstract type AbstractSimpleGraph{T<:Integer} <: AbstractGraph{T, AbstractSimpleEdge{T}} end function show(io::IO, ::MIME"text/plain", g::AbstractSimpleGraph{T}) where T dir = is_directed(g) ? "directed" : "undirected" @@ -85,6 +90,12 @@ badj(x...) = _NI("badj") # handles single-argument edge constructors such as pairs and tuples has_edge(g::AbstractSimpleGraph, x) = has_edge(g, edgetype(g)(x)) add_edge!(g::AbstractSimpleGraph, x) = add_edge!(g, edgetype(g)(x)) +@traitfn get_edges(g::AbstractSimpleGraph::IsDirected, u, v) = has_edge(g, u, v) ? [Edge(u, v)] : Edge[] +@traitfn function get_edges(g::AbstractSimpleGraph::(!IsDirected), u, v) + !has_edge(g, u, v) && return Edge[] + u < v && return [Edge(u, v)] + return [Edge(v, u)] +end # handles two-argument edge constructors like src,dst has_edge(g::AbstractSimpleGraph, x, y) = has_edge(g, edgetype(g)(x, y)) @@ -92,6 +103,11 @@ add_edge!(g::AbstractSimpleGraph, x, y) = add_edge!(g, edgetype(g)(x, y)) inneighbors(g::AbstractSimpleGraph, v::Integer) = badj(g, v) outneighbors(g::AbstractSimpleGraph, v::Integer) = fadj(g, v) +outedges(g::AbstractSimpleGraph, v::Integer) = Edge.(v, outneighbors(g, v)) +inedges(g::AbstractSimpleGraph, v::Integer) = Edge.(v, inneighbors(g, v)) + +get_vertex_container(g::AbstractSimpleGraph, K::Type) = Vector{K}(undef, nv(g)) +# get_edge_container(g::AbstractGraph, K::Type) = Array{K, 2}(undef, (nv(g), nv(g)) function issubset(g::T, h::T) where T <: AbstractSimpleGraph nv(g) <= nv(h) || return false @@ -210,6 +226,8 @@ end zero(::Type{G}) where {G<:AbstractSimpleGraph} = G() +is_range_based(::Type{<:AbstractSimpleGraph}) = true + include("./simpleedge.jl") include("./simpledigraph.jl") include("./simplegraph.jl") diff --git a/src/SimpleGraphs/simpledigraph.jl b/src/SimpleGraphs/simpledigraph.jl index eec1ae701..89a131cc6 100644 --- a/src/SimpleGraphs/simpledigraph.jl +++ b/src/SimpleGraphs/simpledigraph.jl @@ -120,7 +120,7 @@ function SimpleDiGraph{T}(adjmx::AbstractMatrix{U}) where T <: Integer where U < g = SimpleDiGraph(T(dima)) @inbounds for i in findall(adjmx .!= zero(U)) - add_edge!(g, i[1], i[2]) + add_edge!(g, i[1], i[2]) end return g end @@ -214,9 +214,9 @@ function SimpleDiGraph(edge_list::Vector{SimpleDiGraphEdge{T}}) where T <: Integ nvg = zero(T) @inbounds( for e in edge_list - nvg = max(nvg, src(e), dst(e)) + nvg = max(nvg, src(e), dst(e)) end) - + list_sizes_out = ones(Int, nvg) list_sizes_in = ones(Int, nvg) degs_out = zeros(Int, nvg) @@ -228,7 +228,7 @@ function SimpleDiGraph(edge_list::Vector{SimpleDiGraphEdge{T}}) where T <: Integ degs_out[s] += 1 degs_in[d] += 1 end) - + fadjlist = Vector{Vector{T}}(undef, nvg) badjlist = Vector{Vector{T}}(undef, nvg) @inbounds( @@ -241,9 +241,9 @@ function SimpleDiGraph(edge_list::Vector{SimpleDiGraphEdge{T}}) where T <: Integ for e in edge_list s, d = src(e), dst(e) (s >= 1 && d >= 1) || continue - fadjlist[s][list_sizes_out[s]] = d + fadjlist[s][list_sizes_out[s]] = d list_sizes_out[s] += 1 - badjlist[d][list_sizes_in[d]] = s + badjlist[d][list_sizes_in[d]] = s list_sizes_in[d] += 1 end) @@ -286,8 +286,8 @@ function _SimpleDiGraphFromIterator(iter)::SimpleDiGraph T = eltype(e) g = SimpleDiGraph{T}() - fadjlist = Vector{Vector{T}}() - badjlist = Vector{Vector{T}}() + fadjlist = Vector{Vector{T}}() + badjlist = Vector{Vector{T}}() while next != nothing (e, state) = next @@ -314,8 +314,8 @@ end function _SimpleDiGraphFromIterator(iter, ::Type{T}) where {T <: Integer} g = SimpleDiGraph{T}() - fadjlist = Vector{Vector{T}}() - badjlist = Vector{Vector{T}}() + fadjlist = Vector{Vector{T}}() + badjlist = Vector{Vector{T}}() @inbounds( for e in iter @@ -433,14 +433,14 @@ function rem_edge!(g::SimpleDiGraph{T}, e::SimpleDiGraphEdge{T}) where T s, d = T.(Tuple(e)) verts = vertices(g) (s in verts && d in verts) || return false # edge out of bounds - @inbounds list = g.fadjlist[s] + @inbounds list = g.fadjlist[s] index = searchsortedfirst(list, d) @inbounds (index <= length(list) && list[index] == d) || return false # edge not in graph deleteat!(list, index) g.ne -= 1 - @inbounds list = g.badjlist[d] + @inbounds list = g.badjlist[d] index = searchsortedfirst(list, s) deleteat!(list, index) return true # edge successfully removed @@ -484,7 +484,7 @@ function rem_vertices!(g::SimpleDiGraph{T}, end end else - # traverse the vertex list and replace vertices that get removed + # traverse the vertex list and replace vertices that get removed # with the furthest one to the back that does not get removed i = 1 j = length(remove) @@ -580,7 +580,7 @@ function all_neighbors(g::SimpleDiGraph{T}, u::Integer) where T elseif in_nbrs[i] > out_nbrs[j] union_nbrs[indx] = out_nbrs[j] j += 1 - else + else union_nbrs[indx] = out_nbrs[j] i += 1 j += 1 @@ -600,3 +600,9 @@ function all_neighbors(g::SimpleDiGraph{T}, u::Integer) where T resize!(union_nbrs, indx-1) return union_nbrs end + +# defining the Traits +is_simply_mutable(::Type{<:SimpleDiGraph}) = true +is_mutable(::Type{<:SimpleDiGraph}) = true +is_weight_mutable(::Type{<:SimpleDiGraph}) = false +is_vertex_stable(::Type{<:SimpleDiGraph}) = false diff --git a/src/SimpleGraphs/simpleedge.jl b/src/SimpleGraphs/simpleedge.jl index 5c41d14ee..44170b932 100644 --- a/src/SimpleGraphs/simpleedge.jl +++ b/src/SimpleGraphs/simpleedge.jl @@ -1,7 +1,5 @@ import Base: Pair, Tuple, show, ==, hash -import Graphs: AbstractEdge, src, dst, reverse - -abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T} end +import Graphs: src, dst, reverse struct SimpleEdge{T<:Integer} <: AbstractSimpleEdge{T} src::T @@ -30,5 +28,6 @@ SimpleEdge{T}(e::AbstractSimpleEdge) where T <: Integer = SimpleEdge{T}(T(e.src) # Convenience functions reverse(e::T) where T<:AbstractSimpleEdge = T(dst(e), src(e)) +isless(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) = isless(Tuple(e1), Tuple(e2)) ==(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) = (src(e1) == src(e2) && dst(e1) == dst(e2)) hash(e::AbstractSimpleEdge, h::UInt) = hash(src(e), hash(dst(e), h)) diff --git a/src/SimpleGraphs/simplegraph.jl b/src/SimpleGraphs/simplegraph.jl index 1c244fc6c..3ccb3a558 100644 --- a/src/SimpleGraphs/simplegraph.jl +++ b/src/SimpleGraphs/simplegraph.jl @@ -671,3 +671,9 @@ function rem_vertices!(g::SimpleGraph{T}, return reverse_vmap end + +# defining the Traits +is_simply_mutable(::Type{<:SimpleGraph}) = true +is_mutable(::Type{<:SimpleGraph}) = true +is_weight_mutable(::Type{<:SimpleGraph}) = false +is_vertex_stable(::Type{<:SimpleGraph}) = false diff --git a/src/core.jl b/src/core.jl index bba651901..374cabab2 100644 --- a/src/core.jl +++ b/src/core.jl @@ -25,24 +25,124 @@ false """ is_ordered(e::AbstractEdge) = src(e) <= dst(e) + """ - add_vertices!(g, n) + neighbors(g, v) -Add `n` new vertices to the graph `g`. -Return the number of vertices that were added successfully. +Return a list of all neighbors reachable from vertex `v` in `g`. +For directed graphs, the default is equivalent to [`outneighbors`](@ref); +use [`all_neighbors`](@ref) to list inbound and outbound neighbors. + +### Implementation Notes +Returns a reference to the current graph's internal structures, not a copy. +Do not modify result. If the graph is modified, the behavior is undefined: +the array behind this reference may be modified too, but this is not guaranteed. # Examples ```jldoctest julia> using Graphs -julia> g = SimpleGraph() -{0, 0} undirected simple Int64 graph +julia> g = DiGraph(3); -julia> add_vertices!(g, 2) -2 +julia> add_edge!(g, 2, 3); + +julia> add_edge!(g, 3, 1); + +julia> neighbors(g, 1) +0-element Array{Int64,1} + +julia> neighbors(g, 2) +1-element Array{Int64,1}: + 3 + +julia> neighbors(g, 3) +1-element Array{Int64,1}: + 1 ``` """ -add_vertices!(g::AbstractGraph, n::Integer) = sum([add_vertex!(g) for i = 1:n]) +neighbors(g::AbstractGraph, v::Integer) = outneighbors(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 [`outneighbors`](@ref) +and [`inneighbors`](@ref). + +### Implementation Notes +Returns a reference to the current graph's internal structures, not a copy. +Do not modify result. If the graph is modified, the behavior is undefined: +the array behind this reference may be modified too, but this is not guaranteed. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = DiGraph(3); + +julia> add_edge!(g, 2, 3); + +julia> add_edge!(g, 3, 1); + +julia> all_neighbors(g, 1) +1-element Array{Int64,1}: + 3 + +julia> all_neighbors(g, 2) +1-element Array{Int64,1}: + 3 + +julia> all_neighbors(g, 3) +2-element Array{Int64,1}: + 1 + 2 + ``` +""" +function all_neighbors end +@traitfn all_neighbors(g::::IsDirected, v::Integer) = + union(outneighbors(g, v), inneighbors(g, v)) +@traitfn all_neighbors(g::::(!IsDirected), v::Integer) = + neighbors(g, v) + + +""" + common_neighbors(g, u, v) + +Return the neighbors common to vertices `u` and `v` in `g`. + +### Implementation Notes +Returns a reference to the current graph's internal structures, not a copy. +Do not modify result. If the graph is modified, the behavior is undefined: +the array behind this reference may be modified too, but this is not guaranteed. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(4); + +julia> add_edge!(g, 1, 2); + +julia> add_edge!(g, 2, 3); + +julia> add_edge!(g, 3, 4); + +julia> add_edge!(g, 4, 1); + +julia> add_edge!(g, 1, 3); + +julia> common_neighbors(g, 1, 3) +2-element Array{Int64,1}: + 2 + 4 + +julia> common_neighbors(g, 1, 4) +1-element Array{Int64,1}: + 3 +``` +""" +common_neighbors(g::AbstractGraph, u::Integer, v::Integer) = + intersect(neighbors(g, u), neighbors(g, v)) """ indegree(g[, v]) @@ -122,8 +222,8 @@ julia> degree(g) ``` """ function degree end -@traitfn degree(g::::IsDirected, v::Integer) = indegree(g, v) + outdegree(g, v) -@traitfn degree(g::::(!IsDirected), v::Integer) = indegree(g, v) +@traitfn degree(g::AbstractGraph::(!IsDirected), v::Integer) = indegree(g, v) +@traitfn degree(g::AbstractGraph::IsDirected, v::Integer) = indegree(g, v) + outdegree(g, v) degree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [degree(g, x) for x in v] @@ -204,125 +304,6 @@ function degree_histogram(g::AbstractGraph{T}, degfn=degree) where T return hist end - -""" - neighbors(g, v) - -Return a list of all neighbors reachable from vertex `v` in `g`. -For directed graphs, the default is equivalent to [`outneighbors`](@ref); -use [`all_neighbors`](@ref) to list inbound and outbound neighbors. - -### Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: -the array behind this reference may be modified too, but this is not guaranteed. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = DiGraph(3); - -julia> add_edge!(g, 2, 3); - -julia> add_edge!(g, 3, 1); - -julia> neighbors(g, 1) -0-element Array{Int64,1} - -julia> neighbors(g, 2) -1-element Array{Int64,1}: - 3 - -julia> neighbors(g, 3) -1-element Array{Int64,1}: - 1 -``` -""" -neighbors(g::AbstractGraph, v::Integer) = outneighbors(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 [`outneighbors`](@ref) -and [`inneighbors`](@ref). - -### Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: -the array behind this reference may be modified too, but this is not guaranteed. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = DiGraph(3); - -julia> add_edge!(g, 2, 3); - -julia> add_edge!(g, 3, 1); - -julia> all_neighbors(g, 1) -1-element Array{Int64,1}: - 3 - -julia> all_neighbors(g, 2) -1-element Array{Int64,1}: - 3 - -julia> all_neighbors(g, 3) -2-element Array{Int64,1}: - 1 - 2 - ``` -""" -function all_neighbors end -@traitfn all_neighbors(g::::IsDirected, v::Integer) = - union(outneighbors(g, v), inneighbors(g, v)) -@traitfn all_neighbors(g::::(!IsDirected), v::Integer) = - neighbors(g, v) - - -""" - common_neighbors(g, u, v) - -Return the neighbors common to vertices `u` and `v` in `g`. - -### Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: -the array behind this reference may be modified too, but this is not guaranteed. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(4); - -julia> add_edge!(g, 1, 2); - -julia> add_edge!(g, 2, 3); - -julia> add_edge!(g, 3, 4); - -julia> add_edge!(g, 4, 1); - -julia> add_edge!(g, 1, 3); - -julia> common_neighbors(g, 1, 3) -2-element Array{Int64,1}: - 2 - 4 - -julia> common_neighbors(g, 1, 4) -1-element Array{Int64,1}: - 3 -``` -""" -common_neighbors(g::AbstractGraph, u::Integer, v::Integer) = - intersect(neighbors(g, u), neighbors(g, v)) - """ has_self_loops(g) @@ -371,6 +352,26 @@ julia> num_self_loops(g) """ num_self_loops(g::AbstractGraph) = nv(g) == 0 ? 0 : sum(v -> has_edge(g, v, v), vertices(g)) + +""" + add_vertices!(g, n) + +Add `n` new vertices to the graph `g`. +Return the number of vertices that were added successfully. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph() +{0, 0} undirected simple Int64 graph + +julia> add_vertices!(g, 2) +2 +``` +""" +@traitfn add_vertices!(g::AbstractGraph::IsSimplyMutable, n::Integer) = sum([add_vertex!(g) for i = 1:n]) + """ density(g) diff --git a/src/cycles/incremental.jl b/src/cycles/incremental.jl index a027cd2d0..ad3af2464 100644 --- a/src/cycles/incremental.jl +++ b/src/cycles/incremental.jl @@ -10,7 +10,7 @@ constructor IncrementalCycleTracker(G) may be used to automatically select a specific incremental cycle detection algorithm. See [`add_edge_checked!`](@ref) for a usage example. """ -abstract type IncrementalCycleTracker{I} <: AbstractGraph{I} end +abstract type IncrementalCycleTracker{I} <: AbstractGraph{I, AbstractEdge} end function (::Type{IncrementalCycleTracker})(s::AbstractGraph{I}; dir::Union{Symbol,Nothing}=nothing) where {I} # TODO: Once we have more algorithms, the poly-algorithm decision goes here. diff --git a/src/interface.jl b/src/interface.jl index 79839a2e8..ad6897492 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -15,12 +15,23 @@ Base.showerror(io::IO, ie::NotImplementedError) = print(io, "method $(ie.m) not _NI(m) = throw(NotImplementedError(m)) +""" + AbstractVertex + +A trait representing a single vertex. +""" +@traitdef AbstractVertex{V} +@traitimpl AbstractVertex{V} <- is_vertex(V) + """ AbstractEdge An abstract type representing a single edge between two vertices of a graph. +- `V`: Vertex type +- `U`: Weight type """ -abstract type AbstractEdge{T} end +abstract type AbstractEdge{V, U} end +# abstract type AbstractWeightedEdge{V, U} <: AbstractEdge{V} end """ AbstractEdgeIter @@ -32,18 +43,66 @@ abstract type AbstractEdgeIter end """ AbstractGraph -An abstract type representing a graph. +An abstract type representing a multi-graph. +- `V` : Vertex type +- `E` : Edge type + """ -abstract type AbstractGraph{T} end +abstract type AbstractGraph{V, E<:AbstractEdge{V}} end +abstract type AbstractBidirectionalGraph{V, E} <: AbstractGraph{V, E} end @traitdef IsDirected{G<:AbstractGraph} @traitimpl IsDirected{G} <- is_directed(G) +@traitdef IsRangeBased{G<:AbstractGraph} +@traitimpl IsRangeBased{G} <- is_range_based(G) + +@traitdef IsSimplyMutable{G<:AbstractGraph} +@traitimpl IsSimplyMutable{G} <- is_simply_mutable(G) + +@traitdef IsMutable{G<:AbstractGraph} +@traitimpl IsMutable{G} <- is_mutable(G) + +@traitdef IsWeightMutable{G<:AbstractGraph} +@traitimpl IsWeightMutable{G} <- is_weight_mutable(G) + +@traitdef IsVertexStable{G<:AbstractGraph} +@traitimpl IsVertexStable{G} <- is_vertex_stable(G) + +# +# Interface for AbstractVertex +# +import Base.isless#, Base.:(==) +""" + isless(v1, v2) + +Return true if vertex v1 is less than vertex v2 in lexicographic order. +""" +@traitfn Base.isless(v1::V, v2::V) where {V; AbstractVertex{V}} = _NI("src") + +# @traitfn Base.:(==)(v1::V, v2::V) where {V; AbstractVertex{V}} = _NI("==") + +""" + vindex(v) + +Return an index for the vertex `v`. +""" +vindex(v) = _NI("vindex") # -# Interface for AbstractEdges +# Interface for AbstractEdge # +hash(v::AbstractEdge) = _NI("hash") + +""" + isless(e1, e2) + +Return true if edge e1 is less than edge e2 in lexicographic order. +""" +isless(v1::AbstractEdge , v2::AbstractEdge) = _NI("src") + +==(e1::AbstractEdge, e2::AbstractEdge) = _NI("==") """ src(e) @@ -83,6 +142,14 @@ julia> dst(first(edges(g))) """ dst(e::AbstractEdge) = _NI("dst") +""" + weight(e) + +Return the weight of edge `e`. +""" +weight(e::AbstractEdge{V, U}) where {V, U} = one(U) + + Pair(e::AbstractEdge) = _NI("Pair") Tuple(e::AbstractEdge) = _NI("Tuple") @@ -105,8 +172,6 @@ Edge 2 => 1 """ reverse(e::AbstractEdge) = _NI("reverse") -==(e1::AbstractEdge, e2::AbstractEdge) = _NI("==") - # # Interface for AbstractGraphs @@ -116,34 +181,48 @@ reverse(e::AbstractEdge) = _NI("reverse") Return the type of graph `g`'s edge """ -edgetype(g::AbstractGraph) = _NI("edgetype") +edgetype(g::AbstractGraph{V, E}) where {V, E} = E """ eltype(g) -Return the type of the graph's vertices (must be <: Integer) +Return the type of the graph's vertices """ -eltype(g::AbstractGraph) = _NI("eltype") +eltype(g::AbstractGraph{V, E}) where {V, E} = V + """ - nv(g) + vertices(g) -Return the number of vertices in `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`. # Examples ```jldoctest julia> using Graphs -julia> nv(SimpleGraph(3)) -3 +julia> collect(vertices(SimpleGraph(4))) +4-element Array{Int64,1}: + 1 + 2 + 3 + 4 ``` """ -nv(g::AbstractGraph) = _NI("nv") +vertices(g::AbstractGraph) = _NI("vertices") """ - ne(g) + get_edges(g, u, v) -Return the number of edges in `g`. +Return (an iterator to or collection of) the edges of a graph `g` +going from `u` to `v`. + +### Implementation Notes +A returned iterator is valid for one pass over the edges, and +is invalidated by changes to `g`. # Examples ```jldoctest @@ -151,16 +230,20 @@ julia> using Graphs julia> g = path_graph(3); -julia> ne(g) -2 +julia> collect(get_edges(g, 1, 2)) +1-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: + Edge 1 => 2 ``` """ -ne(g::AbstractGraph) = _NI("ne") +@traitfn get_edges(g::AbstractGraph, u::V, v::V) where {V; AbstractVertex{V}} = _NI("get_edges") """ - vertices(g) + edges(g) -Return (an iterator to or collection of) the vertices of a graph. +Return (an iterator to or collection of) the edges of a graph. +For `AbstractSimpleGraph`s it returns a `SimpleEdgeIter`. +The expressions `e in edges(g)` and `e ∈ edges(ga)` evaluate as +calls to [`has_edge`](@ref). ### Implementation Notes A returned iterator is valid for one pass over the edges, and @@ -170,23 +253,21 @@ is invalidated by changes to `g`. ```jldoctest julia> using Graphs -julia> collect(vertices(SimpleGraph(4))) -4-element Array{Int64,1}: - 1 - 2 - 3 - 4 +julia> g = path_graph(3); + +julia> collect(edges(g)) +2-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: + Edge 1 => 2 + Edge 2 => 3 ``` """ -vertices(g::AbstractGraph) = _NI("vertices") +edges(g::AbstractGraph) = _NI("edges") """ - edges(g) + outedges(g, u) -Return (an iterator to or collection of) the edges of a graph. -For `AbstractSimpleGraph`s it returns a `SimpleEdgeIter`. -The expressions `e in edges(g)` and `e ∈ edges(ga)` evaluate as -calls to [`has_edge`](@ref). +Return (an iterator to or collection of) the outcoming edges of a graph `g` +leaving vertex `u`. ### Implementation Notes A returned iterator is valid for one pass over the edges, and @@ -198,13 +279,78 @@ julia> using Graphs julia> g = path_graph(3); -julia> collect(edges(g)) -2-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: +julia> collect(outedges(g, 1)) +1-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: Edge 1 => 2 - Edge 2 => 3 ``` """ -edges(g) = _NI("edges") +@traitfn outedges(g::AbstractGraph, v::V) where {V; AbstractVertex{V}} = _NI("outedges") + +""" + inedges(g, u) + +Return (an iterator to or collection of) the incoming edges of a graph `g` +toward vertex `u`. + +### Implementation Notes +A returned iterator is valid for one pass over the edges, and +is invalidated by changes to `g`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = path_graph(3); + +julia> collect(outedges(g, 1)) +1-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: + Edge 1 => 2 +``` +""" +@traitfn inedges(g::AbstractGraph, v::V) where {V; AbstractVertex{V}} = _NI("outedges") + +""" + nv(g) + +Return the number of vertices in `g`. + +# Examples +```jldoctest +julia> using Graphs + +julia> nv(SimpleGraph(3)) +3 +``` +""" +nv(g::AbstractGraph) = length(vertices(g)) + +""" + ne(g) + +Return the number of edges in `g`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = path_graph(3); + +julia> ne(g) +2 +``` +""" +ne(g::AbstractGraph) = length(edges(g)) + + +""" + is_vertex(G) + +Return `true` if the graph type `V` is an AbstractVertex ; `false` otherwise. +The method can also be called with `is_vertex(v::V)` +""" +is_vertex(::V) where {V} = is_vertex(V) +is_vertex(::Type{T}) where T = _NI("is_vertex") +is_vertex(::Type{<:Integer}) = true """ is_directed(G) @@ -229,6 +375,60 @@ true is_directed(::G) where {G} = is_directed(G) is_directed(::Type{T}) where T = _NI("is_directed") +""" + is_range_based(G) + +Return `true` if the vertex of graph type `G` forms a OneTo range; `false` otherwise. +New graph types must implement `is_range_based(::Type{<:G})`. +The method can also be called with `is_range_based(g::G)` +""" +is_range_based(::G) where {G} = is_range_based(G) +is_range_based(::Type{T}) where T = false + +""" + is_simply_mutable(G) + +Return `true` if the graph type `G` is able to represent the structure +of any unweighted simple graph (with loops); `false` otherwise. +New graph types must implement `is_simply_mutable(::Type{<:G})`. +The method can also be called with `is_simply_mutable(g::G)` +""" +is_simply_mutable(::G) where {G} = is_simply_mutable(G) +is_simply_mutable(::Type{T}) where T = false + +""" + is_mutable(G) + +Return `true` if the graph type `G` is able to represent the structure +of any unweighted multigraph; `false` otherwise. +New graph types must implement `is_mutable(::Type{<:G})`. +The method can also be called with `is_mutable(g::G)` +""" +is_mutable(::G) where {G} = is_mutable(G) +is_mutable(::Type{T}) where T = false + +""" + is_weight_mutable(G) + +Return `true` if the graph type `G` is able to modify any of its weights +(but not necessarily able to modify its structure); `false` otherwise. +New graph types must implement `is_weight_mutable(::Type{<:G})`. +The method can also be called with `is_weight_mutable(g::G)` +""" +is_weight_mutable(::G) where {G} = is_weight_mutable(G) +is_weight_mutable(::Type{T}) where T = false + +""" + is_vertex_stable(G) + +Return `true` if vertices of the graph type `G` are kept when mutating +the graph; `false` otherwise. +New graph types must implement `is_vertex_stable(::Type{<:G})`. +The method can also be called with `is_vertex_stable(g::G)` +""" +is_vertex_stable(::G) where {G} = is_vertex_stable(G) +is_vertex_stable(::Type{T}) where T = false + """ has_vertex(g, v) @@ -245,7 +445,7 @@ julia> has_vertex(SimpleGraph(2), 3) false ``` """ -has_vertex(x, v) = _NI("has_vertex") +has_vertex(g, v) = _NI("has_vertex") """ has_edge(g, s, d) @@ -296,7 +496,7 @@ julia> inneighbors(g, 4) 5 ``` """ -inneighbors(x, v) = _NI("inneighbors") +inneighbors(g, v) = _NI("inneighbors") """ outneighbors(g, v) @@ -317,7 +517,32 @@ julia> outneighbors(g, 4) 5 ``` """ -outneighbors(x, v) = _NI("outneighbors") +outneighbors(g, v) = _NI("outneighbors") + +""" + get_vertex_container(g::AbstractGraph, K::Type) + +Return a container indexed by vertices of 'g' of eltype 'K'. + +# Examples +```jldoctest +julia> c = get_vertex_container(SimpleGraph(5), Int16) + +julia> typeof(c) +Vector{Int16} + +julia> length(c) +5 +``` +""" +get_vertex_container(g::AbstractGraph{V}, K::Type) where V = Dict{V, K}() + +""" + get_edge_container(g::AbstractGraph, K::Type) + +Return a container indexed by edges of 'g' of eltype 'K'. +""" +get_edge_container(g::AbstractGraph{V, E}, K::Type) where {V, E} = Dict{E, K}() """ zero(G) diff --git a/src/shortestpaths/astar.jl b/src/shortestpaths/astar.jl index b81148f41..87c50b134 100644 --- a/src/shortestpaths/astar.jl +++ b/src/shortestpaths/astar.jl @@ -5,55 +5,61 @@ function reconstruct_path!(total_path, # a vector to be filled with the shortest path came_from, # a vector holding the parent of each node in the A* exploration + came_from_edge, # a vector holding the parent of each node in the A* exploration end_idx, # the end vertex g, # the graph - edgetype_to_return::Type{E}=edgetype(g)) where {E<:AbstractEdge} + ) curr_idx = end_idx while came_from[curr_idx] != curr_idx - pushfirst!(total_path, edgetype_to_return(came_from[curr_idx], curr_idx)) + pushfirst!(total_path, came_from_edge[curr_idx]) curr_idx = came_from[curr_idx] end end -function a_star_impl!(g, # the graph +function a_star_impl!(g::AbstractGraph{V, E}, # the graph goal, # the end vertex open_set, # an initialized heap containing the active vertices closed_set, # an (initialized) color-map to indicate status of vertices g_score, # a vector holding g scores for each node + edge_weight, came_from, # a vector holding the parent of each node in the A* exploration - distmx, - heuristic, - edgetype_to_return::Type{E}) where {E<:AbstractEdge} - total_path = Vector{edgetype_to_return}() + came_from_edge, + heuristic,) where {V, E} + + total_path = Vector{E}() @inbounds while !isempty(open_set) current = dequeue!(open_set) if current == goal - reconstruct_path!(total_path, came_from, current, g, edgetype_to_return) + reconstruct_path!(total_path, came_from, came_from_edge, current, g) return total_path end closed_set[current] = true - for neighbor in Graphs.outneighbors(g, current) + for e in outedges(g, current) + neighbor = src(e) == current ? dst(e) : src(e) closed_set[neighbor] && continue - tentative_g_score = g_score[current] + distmx[current, neighbor] + tentative_g_score = g_score[current] + edge_weight(e) if tentative_g_score < g_score[neighbor] g_score[neighbor] = tentative_g_score priority = tentative_g_score + heuristic(neighbor) open_set[neighbor] = priority came_from[neighbor] = current + came_from_edge[neighbor] = e end end + end return total_path end + """ - a_star(g, s, t[, distmx][, heuristic][, edgetype_to_return]) + a_star(g, s, t[, distmx][, heuristic][]) Compute a shortest path using the [A* search algorithm](http://en.wikipedia.org/wiki/A%2A_search_algorithm). @@ -61,29 +67,32 @@ Compute a shortest path using the [A* search algorithm](http://en.wikipedia.org/ - `g::AbstractGraph`: the graph - `s::Integer`: the source vertex - `t::Integer`: the target vertex -- `distmx::AbstractMatrix`: an optional (possibly sparse) `n × n` matrix of edge weights. It is set to `weights(g)` by default (which itself falls back on [`Graphs.DefaultDistance`](@ref)). +- `edge_weight::Function`: an optional function returning the distance between the vertices `u` and `v` in `g` - `heuristic::Function`: an optional function mapping each vertex to a lower estimate of the remaining distance from `v` to `t`. It is set to `v -> 0` by default (which corresponds to Dijkstra's algorithm) -- `edgetype_to_return::Type{E}`: the eltype `E<:AbstractEdge` of the vector of edges returned. It is set to `edgetype(g)` by default. Note that the two-argument constructor `E(u, v)` must be defined, even for weighted edges: if it isn't, consider using `E = Graphs.SimpleEdge`. +- `T::Type` : an optional type for the weights """ -function a_star(g::AbstractGraph{U}, # the g - s::Integer, # the start vertex - t::Integer, # the end vertex - distmx::AbstractMatrix{T}=weights(g), - heuristic::Function=n -> zero(T), - edgetype_to_return::Type{E}=edgetype(g)) where {T, U, E<:AbstractEdge} - # if we do checkbounds here, we can use @inbounds in a_star_impl! - checkbounds(distmx, Base.OneTo(nv(g)), Base.OneTo(nv(g))) +function a_star(g::AbstractGraph{V, E}, # the graph + s, # the start vertex + t, # the end vertex + edge_weight::Function = e -> weight(e), + heuristic::Function=n -> zero(T)) where {V, T, E<:AbstractEdge{V, T}} - open_set = PriorityQueue{U, T}() - enqueue!(open_set, s, 0) - closed_set = zeros(Bool, nv(g)) + open_set = PriorityQueue{V, T}() + enqueue!(open_set, s, 0) - g_score = fill(Inf, nv(g)) + closed_set = get_vertex_container(g, Bool) + g_score = get_vertex_container(g, T) + came_from = get_vertex_container(g, T) + for u in vertices(g) + closed_set[u] = false + g_score[u] = typemax(T) + came_from[u] = s + end g_score[s] = 0 - came_from = fill(-one(s), nv(g)) - came_from[s] = s + + came_from_edge = get_vertex_container(g, E) a_star_impl!( g, @@ -91,9 +100,32 @@ function a_star(g::AbstractGraph{U}, # the g open_set, closed_set, g_score, + edge_weight, came_from, - distmx, + came_from_edge, heuristic, - edgetype_to_return ) end + +""" + a_star(g, s, t[, distmx][, heuristic][]) + +Compute a shortest path using the [A* search algorithm](http://en.wikipedia.org/wiki/A%2A_search_algorithm). + +# Arguments +- `g::AbstractGraph`: the graph +- `s::Integer`: the source vertex +- `t::Integer`: the target vertex +- `distmx::AbstractMatrix`: an optional (possibly sparse) `n × n` matrix of edge weights. It is set to `weights(g)` by default (which itself falls back on [`Graphs.DefaultDistance`](@ref)). +- `heuristic::Function`: an optional function mapping each vertex to a lower estimate of the remaining distance from `v` to `t`. It is set to `v -> 0` by default (which corresponds to Dijkstra's algorithm) +""" +function a_star(g::AbstractSimpleGraph, # the graph + s, # the start vertex + t, # the end vertex + distmx::AbstractMatrix{T}, + heuristic::Function=n -> zero(T)) where {T} + + # if we do checkbounds here, we can use @inbounds in a_star_impl! + checkbounds(distmx, Base.OneTo(nv(g)), Base.OneTo(nv(g))) + return @inbounds a_star(g, s, t, e -> distmx[src(e), dst(e)], heuristic) +end