From 448250e34edd8e6581e8d6881cc54af75ec29c9e Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Thu, 31 Aug 2023 08:54:55 +0200 Subject: [PATCH 1/8] first commit --- Project.toml | 4 + src/GraphsBase.jl | 77 +++- src/SimpleGraphs/SimpleGraphs.jl | 216 +++++++++ src/SimpleGraphs/simpledigraph.jl | 622 ++++++++++++++++++++++++++ src/SimpleGraphs/simpleedge.jl | 35 ++ src/SimpleGraphs/simpleedgeiter.jl | 123 ++++++ src/SimpleGraphs/simplegraph.jl | 684 +++++++++++++++++++++++++++++ src/core.jl | 243 ++++++++++ src/interface.jl | 566 ++++++++++++++++++++++++ src/utils.jl | 73 +++ 10 files changed, 2641 insertions(+), 2 deletions(-) create mode 100644 src/SimpleGraphs/SimpleGraphs.jl create mode 100644 src/SimpleGraphs/simpledigraph.jl create mode 100644 src/SimpleGraphs/simpleedge.jl create mode 100644 src/SimpleGraphs/simpleedgeiter.jl create mode 100644 src/SimpleGraphs/simplegraph.jl create mode 100644 src/core.jl create mode 100644 src/interface.jl create mode 100644 src/utils.jl diff --git a/Project.toml b/Project.toml index fec7d53..439ec6f 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,10 @@ uuid = "ad2ac648-372e-45be-9d57-a550431b71c3" authors = ["JuliaGraphs contributors"] version = "0.1.0-DEV" +[deps] +SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + [compat] julia = "1.6" diff --git a/src/GraphsBase.jl b/src/GraphsBase.jl index 6c5be40..57a60d4 100644 --- a/src/GraphsBase.jl +++ b/src/GraphsBase.jl @@ -1,5 +1,78 @@ module GraphsBase -# Write your package code here. +using SimpleTraits -end + +# import Base: adjoint, write, ==, <, *, ≈, convert, isless, issubset, +# reverse, reverse!, isassigned, getindex, setindex!, show, +# print, copy, in, sum, size, eltype, length, ndims, transpose, +# iterate, eltype, get, Pair, Tuple, zero + +export +# Interface +AbstractVertex, is_vertex, AbstractEdge, AbstractWeightedEdge, AbstractEdgeIter, +AbstractGraph, vertices, edges, edgetype, nv, ne, src, dst, +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, +Edge, Graph, SimpleGraph, SimpleGraphFromIterator, DiGraph, SimpleDiGraphFromIterator, +SimpleDiGraph, + +# core +is_ordered, add_vertices!, indegree, outdegree, degree, +neighbors, all_neighbors, has_self_loops, weights, + +# simplegraphs +add_edge!, add_vertex!, add_vertices!, rem_edge!, rem_vertex!, rem_vertices! + +""" + GraphsBase + +The API for the Graphs ecosystem. + +Simple graphs (not multi- or hypergraphs) are represented in a memory- and +time-efficient manner with adjacency lists and edge sets. Both directed and +undirected graphs are supported via separate types, and conversion is available +from directed to undirected. + +The project goal is to mirror the functionality of robust network and graph +analysis libraries such as NetworkX while being simpler to use and more +efficient than existing Julian graph libraries such as Graphs.jl. It is an +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/Graphs.jl) is available, +and tutorials are available at the +[JuliaGraphsTutorials repository](https://github.com/JuliaGraphs/JuliaGraphsTutorials). +""" +GraphsBase +include("interface.jl") +include("utils.jl") +include("core.jl") +include("SimpleGraphs/SimpleGraphs.jl") + +using .SimpleGraphs +""" + Graph + +A datastruture representing an undirected graph. +""" +const Graph = GraphsBase.SimpleGraphs.SimpleGraph +""" + DiGraph + +A datastruture representing a directed graph. +""" +const DiGraph = GraphsBase.SimpleGraphs.SimpleDiGraph +""" + Edge + +A datastruture representing an edge between two vertices in +a `Graph` or `DiGraph`. +""" +const Edge = GraphsBase.SimpleGraphs.SimpleEdge + +end # module \ No newline at end of file diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl new file mode 100644 index 0000000..841f188 --- /dev/null +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -0,0 +1,216 @@ +module SimpleGraphs + +using SparseArrays +# using LinearAlgebra +using GraphsBase +using SimpleTraits + +import Base: + eltype, show, ==, Pair, Tuple, copy, length, issubset, reverse, zero, in, iterate + +import GraphsBase: + _NI, AbstractGraph, AbstractEdge, AbstractEdgeIter, + 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, + insorted + +export AbstractSimpleGraph, AbstractSimpleEdge, + SimpleEdge, SimpleGraph, SimpleGraphFromIterator, SimpleGraphEdge, + SimpleDiGraph, SimpleDiGraphFromIterator, SimpleDiGraphEdge, + add_vertex!, add_edge!, rem_vertex!, rem_vertices!, rem_edge! + +abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T, Int} end + +""" + AbstractSimpleGraph + +An abstract type representing a simple graph structure. +`AbstractSimpleGraph`s must have the following elements: + - `vertices::UnitRange{Integer}` + - `fadjlist::Vector{Vector{Integer}}` + - `ne::Integer` +""" +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" + print(io, "{$(nv(g)), $(ne(g))} $dir simple $T graph") +end + +nv(g::AbstractSimpleGraph{T}) where T = T(length(fadj(g))) +vertices(g::AbstractSimpleGraph) = Base.OneTo(nv(g)) + +""" + throw_if_invalid_eltype(T) + +Internal function, throw a `DomainError` if `T` is not a concrete type `Integer`. +Can be used in the constructor of AbstractSimpleGraphs, +as Julia's typesystem does not enforce concrete types, which can lead to +problems. E.g `SimpleGraph{Signed}`. +""" +function throw_if_invalid_eltype(T::Type{<:Integer}) + if !isconcretetype(T) + throw(DomainError(T, "Eltype for AbstractSimpleGraph must be concrete type.")) + end +end + + +edges(g::AbstractSimpleGraph) = SimpleEdgeIter(g) + + +fadj(g::AbstractSimpleGraph) = g.fadjlist +fadj(g::AbstractSimpleGraph, v::Integer) = g.fadjlist[v] + + +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)) +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 + for u in vertices(g) + u_nbrs_g = neighbors(g, u) + len_u_nbrs_g = length(u_nbrs_g) + len_u_nbrs_g == 0 && continue + u_nbrs_h = neighbors(h, u) + p = 1 + len_u_nbrs_g > length(u_nbrs_h) && return false + (u_nbrs_g[1] < u_nbrs_h[1] || u_nbrs_g[end] > u_nbrs_h[end]) && return false + @inbounds for v in u_nbrs_h + if v == u_nbrs_g[p] + p == len_u_nbrs_g && break + p += 1 + end + end + p == len_u_nbrs_g || return false + end + return true +end + +has_vertex(g::AbstractSimpleGraph, v::Integer) = v in vertices(g) + +ne(g::AbstractSimpleGraph) = g.ne + +function rem_edge!(g::AbstractSimpleGraph{T}, u::Integer, v::Integer) where T + rem_edge!(g, edgetype(g)(T(u), T(v))) +end + +""" + 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``. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(2); + +julia> rem_vertex!(g, 2) +true + +julia> rem_vertex!(g, 2) +false +``` +""" +function rem_vertex!(g::AbstractSimpleGraph, v::Integer) + v in vertices(g) || return false + n = nv(g) + self_loop_n = false # true if n is self-looped (see #820) + + # remove the in_edges from v + srcs = copy(inneighbors(g, v)) + @inbounds for s in srcs + rem_edge!(g, edgetype(g)(s, v)) + end + # remove the in_edges from the last vertex + neigs = copy(inneighbors(g, n)) + @inbounds for s in neigs + rem_edge!(g, edgetype(g)(s, n)) + end + if v != n + # add the edges from n back to v + @inbounds for s in neigs + if s != n # don't add an edge to the last vertex - see #820. + add_edge!(g, edgetype(g)(s, v)) + else + self_loop_n = true + end + end + end + + if is_directed(g) + # remove the out_edges from v + dsts = copy(outneighbors(g, v)) + @inbounds for d in dsts + rem_edge!(g, edgetype(g)(v, d)) + end + # remove the out_edges from the last vertex + neigs = copy(outneighbors(g, n)) + @inbounds for d in neigs + rem_edge!(g, edgetype(g)(n, d)) + end + if v != n + # add the out_edges back to v + @inbounds for d in neigs + if d != n + add_edge!(g, edgetype(g)(v, d)) + end + end + end + end + if self_loop_n + add_edge!(g, edgetype(g)(v, v)) + end + pop!(g.fadjlist) + if is_directed(g) + pop!(g.badjlist) + end + return true +end + +zero(::Type{G}) where {G<:AbstractSimpleGraph} = G() + +is_range_based(::Type{<:AbstractSimpleGraph}) = true + +include("./simpleedge.jl") +include("./simpledigraph.jl") +include("./simplegraph.jl") +include("./simpleedgeiter.jl") + +end # module \ No newline at end of file diff --git a/src/SimpleGraphs/simpledigraph.jl b/src/SimpleGraphs/simpledigraph.jl new file mode 100644 index 0000000..f8734bd --- /dev/null +++ b/src/SimpleGraphs/simpledigraph.jl @@ -0,0 +1,622 @@ +const SimpleDiGraphEdge = SimpleEdge + +""" + SimpleDiGraph{T} + +A type representing a directed graph. +""" +mutable struct SimpleDiGraph{T<:Integer} <: AbstractSimpleGraph{T} + ne::Int + fadjlist::Vector{Vector{T}} # [src]: (dst, dst, dst) + badjlist::Vector{Vector{T}} # [dst]: (src, src, src) + + function SimpleDiGraph{T}( + ne::Int, fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}} + ) where {T} + throw_if_invalid_eltype(T) + return new(ne, fadjlist, badjlist) + end +end + +function SimpleDiGraph( + ne::Int, fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}} +) where {T} + return SimpleDiGraph{T}(ne, fadjlist, badjlist) +end + + +# DiGraph{UInt8}(6), DiGraph{Int16}(7), DiGraph{Int8}() +""" + SimpleDiGraph{T}(n=0) + +Construct a `SimpleDiGraph{T}` with `n` vertices and 0 edges. +If not specified, the element type `T` is the type of `n`. + +## Examples +```jldoctest +julia> using Graphs + +julia> SimpleDiGraph(UInt8(10)) +{10, 0} directed simple UInt8 graph +``` +""" +function SimpleDiGraph{T}(n::Integer=0) where {T<:Integer} + fadjlist = [Vector{T}() for _ in one(T):n] + badjlist = [Vector{T}() for _ in one(T):n] + return SimpleDiGraph(0, fadjlist, badjlist) +end + +# SimpleDiGraph(6), SimpleDiGraph(0x5) +SimpleDiGraph(n::T) where {T<:Integer} = SimpleDiGraph{T}(n) + +# SimpleDiGraph() +SimpleDiGraph() = SimpleDiGraph{Int}() + +# SimpleDiGraph(UInt8) +""" + SimpleDiGraph(::Type{T}) + +Construct an empty `SimpleDiGraph{T}` with 0 vertices and 0 edges. + +## Examples +```jldoctest +julia> using Graphs + +julia> SimpleDiGraph(UInt8) +{0, 0} directed simple UInt8 graph +``` +""" +SimpleDiGraph(::Type{T}) where {T<:Integer} = SimpleDiGraph{T}(zero(T)) + +# SimpleDiGraph(adjmx) +""" + SimpleDiGraph{T}(adjm::AbstractMatrix) + +Construct a `SimpleDiGraph{T}` from the adjacency matrix `adjm`. +If `adjm[i][j] != 0`, an edge `(i, j)` is inserted. `adjm` must be a square matrix. +The element type `T` can be omitted. + +## Examples +```jldoctest +julia> using Graphs + +julia> A1 = [false true; false false] +2×2 Matrix{Bool}: + 0 1 + 0 0 + +julia> SimpleDiGraph(A1) +{2, 1} directed simple Int64 graph + +julia> A2 = [2 7; 5 0] +2×2 Matrix{Int64}: + 2 7 + 5 0 + +julia> SimpleDiGraph{Int16}(A2) +{2, 3} directed simple Int16 graph +``` +""" +SimpleDiGraph(adjmx::AbstractMatrix) = SimpleDiGraph{Int}(adjmx) + +# sparse adjacency matrix constructor: SimpleDiGraph(adjmx) +function SimpleDiGraph{T}(adjmx::SparseMatrixCSC{U}) where {T<:Integer} where {U<:Real} + dima, dimb = size(adjmx) + isequal(dima, dimb) || + throw(ArgumentError("Adjacency / distance matrices must be square")) + + g = SimpleDiGraph(T(dima)) + maxc = length(adjmx.colptr) + @inbounds for c in 1:(maxc - 1) + for rind in 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 SimpleDiGraph{T}(adjmx::AbstractMatrix{U}) where {T<:Integer} where {U<:Real} + dima, dimb = size(adjmx) + isequal(dima, dimb) || + throw(ArgumentError("Adjacency / distance matrices must be square")) + + g = SimpleDiGraph(T(dima)) + @inbounds for i in findall(adjmx .!= zero(U)) + add_edge!(g, i[1], i[2]) + end + return g +end + +# converts DiGraph{Int} to DiGraph{Int32} +""" + SimpleDiGraph{T}(g::SimpleDiGraph) + +Construct a copy of g. +If the element type `T` is specified, the vertices of `g` are converted to this type. +Otherwise the element type is the same as for `g`. + +## Examples +```jldoctest +julia> using Graphs + +julia> g = complete_digraph(5) +{5, 20} directed simple Int64 graph + +julia> SimpleDiGraph{UInt8}(g) +{5, 20} directed simple UInt8 graph +``` +""" +function 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 + +SimpleDiGraph(g::SimpleDiGraph) = copy(g) + +# constructor from abstract graph: SimpleDiGraph(graph) +""" + SimpleDiGraph(g::AbstractSimpleGraph) + +Construct an directed `SimpleDiGraph` from a graph `g`. +The element type is the same as for `g`. + +## Examples +```jldoctest +julia> using Graphs + +julia> g = path_graph(Int8(5)) +{5, 4} undirected simple Int8 graph + +julia> SimpleDiGraph(g) +{5, 8} directed simple Int8 graph +``` +""" +function SimpleDiGraph(g::AbstractSimpleGraph) + h = SimpleDiGraph(nv(g)) + num_self_loops = sum(v -> has_edge(g, v, v), vertices(g); init = 0) + h.ne = ne(g) * 2 - num_self_loops + h.fadjlist = deepcopy_adjlist(fadj(g)) + h.badjlist = deepcopy_adjlist(badj(g)) + return h +end + +@inbounds function cleanupedges!( + fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}} +) where {T<:Integer} + neg = 0 + for v in 1:length(fadjlist) + if !issorted(fadjlist[v]) + sort!(fadjlist[v]) + end + if !issorted(badjlist[v]) + sort!(badjlist[v]) + end + unique!(fadjlist[v]) + unique!(badjlist[v]) + neg += length(fadjlist[v]) + end + return neg +end + +""" + SimpleDiGraph(edge_list::Vector) + +Construct a `SimpleDiGraph` from a vector of edges. +The element type is taken from the edges in `edge_list`. +The number of vertices is the highest that is used in an edge in `edge_list`. + +### Implementation Notes +This constructor works the fastest when `edge_list` is sorted +by the lexical ordering and does not contain any duplicates. + +### See also +[`SimpleDiGraphFromIterator`](@ref) + +## Examples +```jldoctest +julia> using Graphs + +julia> el = Edge.([ (1, 3), (1, 5), (3, 1) ]) +3-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: + Edge 1 => 3 + Edge 1 => 5 + Edge 3 => 1 + +julia> SimpleDiGraph(el) +{5, 3} directed simple Int64 graph +``` +""" +function SimpleDiGraph(edge_list::Vector{SimpleDiGraphEdge{T}}) where {T<:Integer} + nvg = zero(T) + @inbounds( + for e in edge_list + 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) + degs_in = zeros(Int, nvg) + @inbounds( + for e in edge_list + s, d = src(e), dst(e) + (s >= 1 && d >= 1) || continue + degs_out[s] += 1 + degs_in[d] += 1 + end + ) + + fadjlist = Vector{Vector{T}}(undef, nvg) + badjlist = Vector{Vector{T}}(undef, nvg) + @inbounds( + for v in 1:nvg + fadjlist[v] = Vector{T}(undef, degs_out[v]) + badjlist[v] = Vector{T}(undef, degs_in[v]) + end + ) + + @inbounds( + for e in edge_list + s, d = src(e), dst(e) + (s >= 1 && d >= 1) || continue + fadjlist[s][list_sizes_out[s]] = d + list_sizes_out[s] += 1 + badjlist[d][list_sizes_in[d]] = s + list_sizes_in[d] += 1 + end + ) + + neg = cleanupedges!(fadjlist, badjlist) + g = SimpleDiGraph{T}() + g.fadjlist = fadjlist + g.badjlist = badjlist + g.ne = neg + + return g +end + +@inbounds function add_to_lists!( + fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}}, s::T, d::T +) where {T<:Integer} + nvg = length(fadjlist) + nvg_new = max(nvg, s, d) + for v in (nvg + 1):nvg_new + push!(fadjlist, Vector{T}()) + push!(badjlist, Vector{T}()) + end + + push!(fadjlist[s], d) + return push!(badjlist[d], s) +end + +# Try to get the eltype from the first element +function _SimpleDiGraphFromIterator(iter)::SimpleDiGraph + next = iterate(iter) + if (next === nothing) + return SimpleDiGraph(0) + end + + e = first(next) + E = typeof(e) + if !(E <: SimpleGraphEdge{<:Integer}) + throw(DomainError(iter, "Edges must be of type SimpleEdge{T <: Integer}")) + end + + T = eltype(e) + g = SimpleDiGraph{T}() + fadjlist = Vector{Vector{T}}() + badjlist = Vector{Vector{T}}() + + while next != nothing + (e, state) = next + + if !(e isa E) + throw(DomainError(iter, "Edges must all have the same type.")) + end + s, d = src(e), dst(e) + if ((s >= 1) & (d >= 1)) + add_to_lists!(fadjlist, badjlist, s, d) + end + + next = iterate(iter, state) + end + + neg = cleanupedges!(fadjlist, badjlist) + g.fadjlist = fadjlist + g.badjlist = badjlist + g.ne = neg + + return g +end + +function _SimpleDiGraphFromIterator(iter, ::Type{T}) where {T<:Integer} + g = SimpleDiGraph{T}() + fadjlist = Vector{Vector{T}}() + badjlist = Vector{Vector{T}}() + + @inbounds( + for e in iter + s, d = src(e), dst(e) + (s >= 1 && d >= 1) || continue + add_to_lists!(fadjlist, badjlist, s, d) + end + ) + + neg = cleanupedges!(fadjlist, badjlist) + g.fadjlist = fadjlist + g.badjlist = badjlist + g.ne = neg + + return g +end + +""" + SimpleDiGraphFromIterator(iter) + +Create a `SimpleDiGraph` from an iterator `iter`. The elements in `iter` must +be of `type <: SimpleEdge`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleDiGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> add_edge!(g, 2, 1); + +julia> h = SimpleDiGraphFromIterator(edges(g)) +{2, 2} directed simple Int64 graph + +julia> collect(edges(h)) +2-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: + Edge 1 => 2 + Edge 2 => 1 +``` +""" +function SimpleDiGraphFromIterator(iter)::SimpleDiGraph + if Base.IteratorEltype(iter) == Base.HasEltype() + E = eltype(iter) + if (E <: SimpleGraphEdge{<:Integer} && isconcretetype(E)) + T = eltype(E) + if isconcretetype(T) + return _SimpleDiGraphFromIterator(iter, T) + end + end + end + + return _SimpleDiGraphFromIterator(iter) +end + +edgetype(::SimpleDiGraph{T}) where {T<:Integer} = SimpleGraphEdge{T} + +badj(g::SimpleDiGraph) = g.badjlist +badj(g::SimpleDiGraph, v::Integer) = badj(g)[v] + +function copy(g::SimpleDiGraph{T}) where {T<:Integer} + return SimpleDiGraph{T}( + g.ne, deepcopy_adjlist(g.fadjlist), deepcopy_adjlist(g.badjlist) + ) +end + +function ==(g::SimpleDiGraph, h::SimpleDiGraph) + return vertices(g) == vertices(h) && + ne(g) == ne(h) && + fadj(g) == fadj(h) && + badj(g) == badj(h) +end + +is_directed(::Type{<:SimpleDiGraph}) = true + +function has_edge(g::SimpleDiGraph{T}, s, d) where {T} + verts = vertices(g) + (s in verts && d in verts) || return false # edge out of bounds + @inbounds list = g.fadjlist[s] + @inbounds list_backedge = g.badjlist[d] + if length(list) > length(list_backedge) + d = s + list = list_backedge + end + return insorted(d, list) +end + +function has_edge(g::SimpleDiGraph{T}, e::SimpleDiGraphEdge{T}) where {T} + s, d = T.(Tuple(e)) + return has_edge(g, s, d) +end + +function add_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] + index = searchsortedfirst(list, d) + @inbounds (index <= length(list) && list[index] == d) && return false # edge already in graph + insert!(list, index, d) + + g.ne += 1 + + @inbounds list = g.badjlist[d] + index = searchsortedfirst(list, s) + insert!(list, index, s) + return true # edge successfully added +end + +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] + 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] + index = searchsortedfirst(list, s) + deleteat!(list, index) + return true # edge successfully removed +end + +function add_vertex!(g::SimpleDiGraph{T}) where {T} + (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 rem_vertices!( + g::SimpleDiGraph{T}, vs::AbstractVector{<:Integer}; keep_order::Bool=false +) where {T<:Integer} + # check the implementation in simplegraph.jl for more comments + + n = nv(g) + isempty(vs) && return collect(Base.OneTo(n)) + + # Sort and filter the vertices that we want to remove + remove = sort(vs) + unique!(remove) + (1 <= remove[1] && remove[end] <= n) || + throw(ArgumentError("Vertices to be removed must be in the range 1:nv(g).")) + + # Create a vmap that maps vertices to their new position + # vertices that get removed are mapped to 0 + vmap = Vector{T}(undef, n) + if keep_order + # traverse the vertex list and shift if a vertex gets removed + i = 1 + @inbounds for u in vertices(g) + if i <= length(remove) && u == remove[i] + vmap[u] = 0 + i += 1 + else + vmap[u] = u - (i - 1) + end + end + else + # 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) + v = n + @inbounds for u in vertices(g) + u > v && break + if i <= length(remove) && u == remove[i] + while v == remove[j] && v > u + vmap[v] = 0 + v -= one(T) + j -= 1 + end + # v > remove[j] || u == v + vmap[v] = u + vmap[u] = 0 + v -= one(T) + i += 1 + else + vmap[u] = u + end + end + end + + fadjlist = g.fadjlist + badjlist = g.badjlist + + # count the number of edges that will be removed + num_removed_edges = 0 + @inbounds for u in remove + for v in fadjlist[u] + num_removed_edges += 1 + end + for v in badjlist[u] + if vmap[v] != 0 + num_removed_edges += 1 + end + end + end + g.ne -= num_removed_edges + + # move the lists in the adjacency list to their new position + # order of traversing is important! + @inbounds for u in (keep_order ? (one(T):1:n) : (n:-1:one(T))) + if vmap[u] != 0 + fadjlist[vmap[u]] = fadjlist[u] + badjlist[vmap[u]] = badjlist[u] + end + end + resize!(fadjlist, n - length(remove)) + resize!(badjlist, n - length(remove)) + + # remove vertices from the lists in fadjlist and badjlist + @inbounds for list_of_lists in (fadjlist, badjlist) + for list in list_of_lists + Δ = 0 + for (i, v) in enumerate(list) + if vmap[v] == 0 + Δ += 1 + else + list[i - Δ] = vmap[v] + end + end + resize!(list, length(list) - Δ) + if !keep_order + sort!(list) + end + end + end + + # we create a reverse vmap, that maps vertices in the result graph + # to the ones in the original graph. This resembles the output of + # induced_subgraph + reverse_vmap = Vector{T}(undef, nv(g)) + @inbounds for (i, u) in enumerate(vmap) + if u != 0 + reverse_vmap[u] = i + end + end + + return reverse_vmap +end + +function all_neighbors(g::SimpleDiGraph{T}, u::Integer) where {T} + i, j = 1, 1 + in_nbrs, out_nbrs = inneighbors(g, u), outneighbors(g, u) + in_len, out_len = length(in_nbrs), length(out_nbrs) + union_nbrs = Vector{T}(undef, in_len + out_len) + indx = 1 + @inbounds while i <= in_len && j <= out_len + if in_nbrs[i] < out_nbrs[j] + union_nbrs[indx] = in_nbrs[i] + i += 1 + elseif in_nbrs[i] > out_nbrs[j] + union_nbrs[indx] = out_nbrs[j] + j += 1 + else + union_nbrs[indx] = out_nbrs[j] + i += 1 + j += 1 + end + indx += 1 + end + @inbounds while i <= in_len + union_nbrs[indx] = in_nbrs[i] + i += 1 + indx += 1 + end + @inbounds while j <= out_len + union_nbrs[indx] = out_nbrs[j] + j += 1 + indx += 1 + end + resize!(union_nbrs, indx - 1) + return union_nbrs +end diff --git a/src/SimpleGraphs/simpleedge.jl b/src/SimpleGraphs/simpleedge.jl new file mode 100644 index 0000000..d09bf67 --- /dev/null +++ b/src/SimpleGraphs/simpleedge.jl @@ -0,0 +1,35 @@ +import Base: Pair, Tuple, show, ==, hash, isless +import GraphsBase: AbstractEdge, src, dst + +struct SimpleEdge{T<:Integer} <: AbstractSimpleEdge{T} + src::T + dst::T +end + +SimpleEdge(t::Tuple) = SimpleEdge(t[1], t[2]) +SimpleEdge(p::Pair) = SimpleEdge(p.first, p.second) +SimpleEdge{T}(p::Pair) where {T<:Integer} = SimpleEdge(T(p.first), T(p.second)) +SimpleEdge{T}(t::Tuple) where {T<:Integer} = SimpleEdge(T(t[1]), T(t[2])) + +eltype(::Type{<:ET}) where {ET<:AbstractSimpleEdge{T}} where {T} = T + +# 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)) + +SimpleEdge{T}(e::AbstractSimpleEdge) where {T<:Integer} = SimpleEdge{T}(T(e.src), T(e.dst)) + +# Convenience functions +reverse(e::T) where {T<:AbstractSimpleEdge} = T(dst(e), src(e)) +function ==(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) + return (src(e1) == src(e2) && dst(e1) == dst(e2)) +end +hash(e::AbstractSimpleEdge, h::UInt) = hash(src(e), hash(dst(e), h)) +isless(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) = (src(e1) < src(e2)) || ((src(e1) == src(e2)) && (dst(e1) < dst(e2))) diff --git a/src/SimpleGraphs/simpleedgeiter.jl b/src/SimpleGraphs/simpleedgeiter.jl new file mode 100644 index 0000000..079981d --- /dev/null +++ b/src/SimpleGraphs/simpleedgeiter.jl @@ -0,0 +1,123 @@ +""" + SimpleEdgeIter + +The function [`edges`](@ref) returns a `SimpleEdgeIter` for `AbstractSimpleGraph`s. +The iterates are in lexicographical order, smallest first. The iterator is valid for +one pass over the edges, and is invalidated by changes to the graph. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = path_graph(3); + +julia> es = edges(g) +SimpleEdgeIter 2 + +julia> e_it = iterate(es) +(Edge 1 => 2, (1, 2)) + +julia> iterate(es, e_it[2]) +(Edge 2 => 3, (2, 3)) +``` +""" +struct SimpleEdgeIter{G} <: AbstractEdgeIter + g::G +end + +eltype(::Type{SimpleEdgeIter{SimpleGraph{T}}}) where {T} = SimpleGraphEdge{T} +eltype(::Type{SimpleEdgeIter{SimpleDiGraph{T}}}) where {T} = SimpleDiGraphEdge{T} + +@traitfn @inline function iterate( + eit::SimpleEdgeIter{G}, state=(one(eltype(eit.g)), 1) +) where {G <: AbstractSimpleGraph; !IsDirected{G}} + g = eit.g + fadjlist = fadj(g) + T = eltype(g) + n = T(nv(g)) + u, i = state + + @inbounds while u < n + list_u = fadjlist[u] + if i > length(list_u) + u += one(u) + i = searchsortedfirst(fadjlist[u], u) + continue + end + e = SimpleEdge(u, list_u[i]) + state = (u, i + 1) + return e, state + end + + @inbounds (n == 0 || i > length(fadjlist[n])) && return nothing + + e = SimpleEdge(n, n) + state = (u, i + 1) + return e, state +end + +@traitfn @inline function iterate( + eit::SimpleEdgeIter{G}, state=(one(eltype(eit.g)), 1) +) where {G <: AbstractSimpleGraph; IsDirected{G}} + g = eit.g + fadjlist = fadj(g) + T = eltype(g) + n = T(nv(g)) + u, i = state + + n == 0 && return nothing + + @inbounds while true + list_u = fadjlist[u] + if i > length(list_u) + u == n && return nothing + + u += one(u) + list_u = fadjlist[u] + i = 1 + continue + end + e = SimpleEdge(u, list_u[i]) + state = (u, i + 1) + return e, state + end + + return nothing +end + +length(eit::SimpleEdgeIter) = ne(eit.g) + +function _isequal(e1::SimpleEdgeIter, e2) + k = 0 + for e in e2 + has_edge(e1.g, e) || return false + k += 1 + end + return k == ne(e1.g) +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) + g = e1.g + h = e2.g + ne(g) == ne(h) || return false + m = min(nv(g), nv(h)) + for i in 1:m + fadj(g, i) == fadj(h, i) || return false + end + nv(g) == nv(h) && return true + for i in (m + 1):nv(g) + isempty(fadj(g, i)) || return false + end + for i in (m + 1):nv(h) + isempty(fadj(h, i)) || return false + end + return true +end + +in(e, es::SimpleEdgeIter) = has_edge(es.g, e) + +show(io::IO, eit::SimpleEdgeIter) = write(io, "SimpleEdgeIter $(ne(eit.g))") diff --git a/src/SimpleGraphs/simplegraph.jl b/src/SimpleGraphs/simplegraph.jl new file mode 100644 index 0000000..359c399 --- /dev/null +++ b/src/SimpleGraphs/simplegraph.jl @@ -0,0 +1,684 @@ +const SimpleGraphEdge = SimpleEdge + +""" + SimpleGraph{T} + +A type representing an undirected graph. +""" +mutable struct SimpleGraph{T<:Integer} <: AbstractSimpleGraph{T} + ne::Int + fadjlist::Vector{Vector{T}} # [src]: (dst, dst, dst) + + function SimpleGraph{T}(ne::Int, fadjlist::Vector{Vector{T}}) where {T} + throw_if_invalid_eltype(T) + return new{T}(ne, fadjlist) + end +end + +function SimpleGraph(ne, fadjlist::Vector{Vector{T}}) where {T} + return SimpleGraph{T}(ne, fadjlist) +end + +# Graph{UInt8}(6), Graph{Int16}(7), Graph{UInt8}() +""" + SimpleGraph{T}(n=0) + +Construct a `SimpleGraph{T}` with `n` vertices and 0 edges. +If not specified, the element type `T` is the type of `n`. + +## Examples +```jldoctest +julia> using Graphs + +julia> SimpleGraph(UInt8(10)) +{10, 0} undirected simple UInt8 graph +``` +""" +function SimpleGraph{T}(n::Integer=0) where {T<:Integer} + fadjlist = [Vector{T}() for _ in one(T):n] + return SimpleGraph{T}(0, fadjlist) +end + +# SimpleGraph(6), SimpleGraph(0x5) +SimpleGraph(n::T) where {T<:Integer} = SimpleGraph{T}(n) + +# SimpleGraph() +SimpleGraph() = SimpleGraph{Int}() + +# SimpleGraph(UInt8) +""" + SimpleGraph(::Type{T}) + +Construct an empty `SimpleGraph{T}` with 0 vertices and 0 edges. + +## Examples +```jldoctest +julia> using Graphs + +julia> SimpleGraph(UInt8) +{0, 0} undirected simple UInt8 graph +``` +""" +SimpleGraph(::Type{T}) where {T<:Integer} = SimpleGraph{T}(zero(T)) + +# SimpleGraph(adjmx) +""" + SimpleGraph{T}(adjm::AbstractMatrix) + +Construct a `SimpleGraph{T}` from the adjacency matrix `adjm`. +If `adjm[i][j] != 0`, an edge `(i, j)` is inserted. `adjm` must be a square and symmetric matrix. +The element type `T` can be omitted. + +## Examples +```jldoctest +julia> using Graphs + +julia> A1 = [false true; true false]; + +julia> SimpleGraph(A1) +{2, 1} undirected simple Int64 graph + +julia> A2 = [2 7; 7 0]; + +julia> SimpleGraph{Int16}(A2) +{2, 2} undirected simple Int16 graph +``` +""" +SimpleGraph(adjmx::AbstractMatrix) = SimpleGraph{Int}(adjmx) + +# Graph{UInt8}(adjmx) +function SimpleGraph{T}(adjmx::AbstractMatrix) where {T<:Integer} + dima, dimb = size(adjmx) + isequal(dima, dimb) || + throw(ArgumentError("Adjacency / distance matrices must be square")) + issymmetric(adjmx) || + throw(ArgumentError("Adjacency / distance matrices must be symmetric")) + + g = SimpleGraph(T(dima)) + @inbounds for i in findall(triu(adjmx) .!= 0) + add_edge!(g, i[1], i[2]) + end + return g +end + +# SimpleGraph of a SimpleGraph +""" + SimpleGraph{T}(g::SimpleGraph) + +Construct a copy of g. +If the element type `T` is specified, the vertices of `g` are converted to this type. +Otherwise the element type is the same as for `g`. + +## Examples +```jldoctest +julia> using Graphs + +julia> g = complete_graph(5) +{5, 10} undirected simple Int64 graph + +julia> SimpleGraph{UInt8}(g) +{5, 10} undirected simple UInt8 graph +``` +""" +SimpleGraph(g::SimpleGraph) = copy(g) + +# converts Graph{Int} to Graph{Int32} +function SimpleGraph{T}(g::SimpleGraph) where {T<:Integer} + h_fadj = [Vector{T}(x) for x in fadj(g)] + return SimpleGraph(ne(g), h_fadj) +end + +# SimpleGraph(digraph) +""" + SimpleGraph(g::SimpleDiGraph) + +Construct an undirected `SimpleGraph` from a directed `SimpleDiGraph`. +Every directed edge in `g` is added as an undirected edge. +The element type is the same as for `g`. + +## Examples +```jldoctest +julia> using Graphs + +julia> g = path_digraph(Int8(5)) +{5, 4} directed simple Int8 graph + +julia> SimpleGraph(g) +{5, 4} undirected simple Int8 graph +``` +""" +function SimpleGraph(g::SimpleDiGraph) + gnv = nv(g) + edgect = 0 + newfadj = deepcopy_adjlist(g.fadjlist) + @inbounds for i in vertices(g) + for j in badj(g, i) + index = searchsortedfirst(newfadj[i], j) + if index <= length(newfadj[i]) && newfadj[i][index] == j + edgect += 1 # this is an existing edge - we already have it + if i == j + edgect += 1 # need to count self loops + end + else + insert!(newfadj[i], index, j) + edgect += 2 # this is a new edge only in badjlist + end + end + end + iseven(edgect) || + throw(AssertionError("invalid edgect in graph creation - please file bug report")) + return SimpleGraph(edgect ÷ 2, newfadj) +end + +@inbounds function cleanupedges!(fadjlist::Vector{Vector{T}}) where {T<:Integer} + neg = 0 + for v in 1:length(fadjlist) + if !issorted(fadjlist[v]) + sort!(fadjlist[v]) + end + unique!(fadjlist[v]) + neg += length(fadjlist[v]) + # self-loops should count as one edge + for w in fadjlist[v] + if w == v + neg += 1 + break + end + end + end + return neg ÷ 2 +end + +""" + SimpleGraph(edge_list::Vector) + +Construct a `SimpleGraph` from a vector of edges. +The element type is taken from the edges in `edge_list`. +The number of vertices is the highest that is used in an edge in `edge_list`. + +### Implementation Notes +This constructor works the fastest when `edge_list` is sorted +by the lexical ordering and does not contain any duplicates. + +### See also +[`SimpleGraphFromIterator`](@ref) + +## Examples +```jldoctest +julia> using Graphs + +julia> el = Edge.([ (1, 2), (1, 5) ]) +2-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: + Edge 1 => 2 + Edge 1 => 5 + +julia> SimpleGraph(el) +{5, 2} undirected simple Int64 graph +``` +""" +function SimpleGraph(edge_list::Vector{SimpleGraphEdge{T}}) where {T<:Integer} + nvg = zero(T) + @inbounds( + for e in edge_list + nvg = max(nvg, src(e), dst(e)) + end + ) + + list_sizes = ones(Int, nvg) + degs = zeros(Int, nvg) + @inbounds( + for e in edge_list + s, d = src(e), dst(e) + (s >= 1 && d >= 1) || continue + degs[s] += 1 + if s != d + degs[d] += 1 + end + end + ) + + fadjlist = Vector{Vector{T}}(undef, nvg) + @inbounds( + for v in 1:nvg + fadjlist[v] = Vector{T}(undef, degs[v]) + end + ) + + @inbounds( + for e in edge_list + s, d = src(e), dst(e) + (s >= 1 && d >= 1) || continue + fadjlist[s][list_sizes[s]] = d + list_sizes[s] += 1 + if s != d + fadjlist[d][list_sizes[d]] = s + list_sizes[d] += 1 + end + end + ) + + neg = cleanupedges!(fadjlist) + g = SimpleGraph{T}() + g.fadjlist = fadjlist + g.ne = neg + + return g +end + +@inbounds function add_to_fadjlist!( + fadjlist::Vector{Vector{T}}, s::T, d::T +) where {T<:Integer} + nvg = length(fadjlist) + nvg_new = max(nvg, s, d) + for v in (nvg + 1):nvg_new + push!(fadjlist, Vector{T}()) + end + + push!(fadjlist[s], d) + if s != d + push!(fadjlist[d], s) + end +end + +# Try to get the eltype from the first element +function _SimpleGraphFromIterator(iter)::SimpleGraph + next = iterate(iter) + if (next === nothing) + return SimpleGraph(0) + end + + e = first(next) + E = typeof(e) + if !(E <: SimpleGraphEdge{<:Integer}) + throw(DomainError(iter, "Edges must be of type SimpleEdge{T <: Integer}")) + end + + T = eltype(e) + g = SimpleGraph{T}() + fadjlist = Vector{Vector{T}}() + + while next != nothing + (e, state) = next + + if !(e isa E) + throw(DomainError(iter, "Edges must all have the same type.")) + end + s, d = src(e), dst(e) + if ((s >= 1) & (d >= 1)) + add_to_fadjlist!(fadjlist, s, d) + end + + next = iterate(iter, state) + end + + neg = cleanupedges!(fadjlist) + g.fadjlist = fadjlist + g.ne = neg + + return g +end + +function _SimpleGraphFromIterator(iter, ::Type{T}) where {T<:Integer} + g = SimpleGraph{T}() + fadjlist = Vector{Vector{T}}() + + @inbounds( + for e in iter + s, d = src(e), dst(e) + (s >= 1 && d >= 1) || continue + add_to_fadjlist!(fadjlist, s, d) + end + ) + + neg = cleanupedges!(fadjlist) + g.fadjlist = fadjlist + g.ne = neg + + return g +end + +""" + SimpleGraphFromIterator(iter) + +Create a [`SimpleGraph`](@ref) from an iterator `iter`. The elements in iter must +be of `type <: SimpleEdge`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(3); + +julia> add_edge!(g, 1, 2); + +julia> add_edge!(g, 2, 3); + +julia> h = SimpleGraphFromIterator(edges(g)); + +julia> collect(edges(h)) +2-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: + Edge 1 => 2 + Edge 2 => 3 +``` +""" +function SimpleGraphFromIterator(iter)::SimpleGraph + if Base.IteratorEltype(iter) == Base.HasEltype() + E = eltype(iter) + if (E <: SimpleGraphEdge{<:Integer} && isconcretetype(E)) + T = eltype(E) + if isconcretetype(T) + return _SimpleGraphFromIterator(iter, T) + end + end + end + + return _SimpleGraphFromIterator(iter) +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 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. +""" +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 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. +""" +adj(g::SimpleGraph) = fadj(g) +adj(g::SimpleGraph, v::Integer) = fadj(g, v) + +copy(g::SimpleGraph) = SimpleGraph(g.ne, deepcopy_adjlist(g.fadjlist)) + +function ==(g::SimpleGraph, h::SimpleGraph) + return vertices(g) == vertices(h) && ne(g) == ne(h) && fadj(g) == fadj(h) +end + +""" + is_directed(g) + +Return `true` if `g` is a directed graph. +""" +is_directed(::Type{<:SimpleGraph}) = false + +function has_edge(g::SimpleGraph{T}, s, d) where {T} + verts = vertices(g) + (s in verts && d in verts) || return false # edge out of bounds + @inbounds list_s = g.fadjlist[s] + @inbounds list_d = g.fadjlist[d] + if length(list_s) > length(list_d) + d = s + list_s = list_d + end + return insorted(d, list_s) +end + +function has_edge(g::SimpleGraph{T}, e::SimpleGraphEdge{T}) where {T} + s, d = T.(Tuple(e)) + return has_edge(g, s, d) +end + +""" + add_edge!(g, e) + +Add an edge `e` to graph `g`. Return `true` if edge was added successfully, +otherwise return `false`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(2); + +julia> add_edge!(g, 1, 2) +true + +julia> add_edge!(g, 2, 3) +false +``` +""" +function add_edge!(g::SimpleGraph{T}, e::SimpleGraphEdge{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] + index = searchsortedfirst(list, d) + @inbounds (index <= length(list) && list[index] == d) && return false # edge already in graph + insert!(list, index, d) + + g.ne += 1 + s == d && return true # selfloop + + @inbounds list = g.fadjlist[d] + index = searchsortedfirst(list, s) + insert!(list, index, s) + return true # edge successfully added +end + +""" + rem_edge!(g, e) + +Remove an edge `e` from graph `g`. Return `true` if edge was removed successfully, +otherwise return `false`. + +### Implementation Notes +If `rem_edge!` returns `false`, the graph may be in an indeterminate state, as +there are multiple points where the function can exit with `false`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> rem_edge!(g, 1, 2) +true + +julia> rem_edge!(g, 1, 2) +false +``` +""" +function rem_edge!(g::SimpleGraph{T}, e::SimpleGraphEdge{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] + index = searchsortedfirst(list, d) + @inbounds (index <= length(list) && list[index] == d) || return false # edge not in graph + deleteat!(list, index) + + g.ne -= 1 + s == d && return true # selfloop + + @inbounds list = g.fadjlist[d] + index = searchsortedfirst(list, s) + deleteat!(list, index) + return true # edge successfully removed +end + +fd +""" + add_vertex!(g) + +Add a new vertex to the graph `g`. Return `true` if addition was successful. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(Int8(typemax(Int8) - 1)) +{126, 0} undirected simple Int8 graph + +julia> add_vertex!(g) +true + +julia> add_vertex!(g) +false +``` +""" +function add_vertex!(g::SimpleGraph{T}) where {T} + (nv(g) + one(T) <= nv(g)) && return false # test for overflow + push!(g.fadjlist, Vector{T}()) + return true +end + +""" + rem_vertices!(g, vs, keep_order=false) -> vmap + +Remove all vertices in `vs` from `g`. +Return a vector `vmap` that maps the vertices in the modified graph to the ones in +the unmodified graph. +If `keep_order` is `true`, the vertices in the modified graph appear in the same +order as they did in the unmodified graph. This might be slower. + +### Implementation Notes +This function is not part of the official Graphs API and is subject to change/removal between major versions. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = complete_graph(5) +{5, 10} undirected simple Int64 graph + +julia> vmap = rem_vertices!(g, [2, 4], keep_order=true); + +julia> vmap +3-element Vector{Int64}: + 1 + 3 + 5 + +julia> g +{3, 3} undirected simple Int64 graph +``` +""" +function rem_vertices!( + g::SimpleGraph{T}, vs::AbstractVector{<:Integer}; keep_order::Bool=false +) where {T<:Integer} + # TODO There might be some room for performance improvements. + # At the moment, we check for all edges if they stay in the graph. + # If some vertices keep their position, this might be unnecessary. + + n = nv(g) + isempty(vs) && return collect(Base.OneTo(n)) + + # Sort and filter the vertices that we want to remove + remove = sort(vs) + unique!(remove) + (1 <= remove[1] && remove[end] <= n) || + throw(ArgumentError("Vertices to be removed must be in the range 1:nv(g).")) + + # Create a vmap that maps vertices to their new position + # vertices that get removed are mapped to 0 + vmap = Vector{T}(undef, n) + + if keep_order + # traverse the vertex list and shift if a vertex gets removed + i = 1 + @inbounds for u in vertices(g) + if i <= length(remove) && u == remove[i] + vmap[u] = 0 + i += 1 + else + vmap[u] = u - (i - 1) + end + end + else + # 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) + v = n + @inbounds for u in vertices(g) + u > v && break + if i <= length(remove) && u == remove[i] + while v == remove[j] && v > u + vmap[v] = 0 + v -= one(T) + j -= 1 + end + # v > remove[j] || u == v + vmap[v] = u + vmap[u] = 0 + v -= one(T) + i += 1 + else + vmap[u] = u + end + end + end + + fadjlist = g.fadjlist + + # count the number of edges that will be removed + # for an edge that gets removed we have to ensure that + # such an edge does not get counted twice when both endpoints + # get removed. That's why we relay on the ordering >= on the vertices. + num_removed_edges = 0 + @inbounds for u in remove + for v in fadjlist[u] + if v >= u || vmap[v] != 0 + num_removed_edges += 1 + end + end + end + g.ne -= num_removed_edges + + # move the lists in the adjacency list to their new position + # The order of traversal is very important here, as otherwise we + # could overwrite lists, that we want to keep! + @inbounds for u in (keep_order ? (one(T):1:n) : (n:-1:one(T))) + if vmap[u] != 0 + fadjlist[vmap[u]] = fadjlist[u] + end + end + resize!(fadjlist, n - length(remove)) + + # remove vertices from the lists in fadjlist + @inbounds for list in fadjlist + Δ = 0 + for (i, v) in enumerate(list) + if vmap[v] == 0 + Δ += 1 + else + list[i - Δ] = vmap[v] + end + end + resize!(list, length(list) - Δ) + if !keep_order + sort!(list) + end + end + + # we create a reverse vmap, that maps vertices in the result graph + # to the ones in the original graph. This resembles the output of + # induced_subgraph + reverse_vmap = Vector{T}(undef, nv(g)) + @inbounds for (i, u) in enumerate(vmap) + if u != 0 + reverse_vmap[u] = i + end + end + + return reverse_vmap +end diff --git a/src/core.jl b/src/core.jl new file mode 100644 index 0000000..709a273 --- /dev/null +++ b/src/core.jl @@ -0,0 +1,243 @@ +""" + AbstractPathState + +An abstract type that provides information from shortest paths calculations. +""" +abstract type AbstractPathState end + +""" + is_ordered(e) + +Return true if the source vertex of edge `e` is less than or equal to +the destination vertex. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = DiGraph(2); + +julia> add_edge!(g, 2, 1); + +julia> is_ordered(first(edges(g))) +false +``` +""" +is_ordered(e::AbstractEdge) = src(e) <= dst(e) + + +""" + 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) + +""" + indegree(g[, v]) + +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`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = DiGraph(3); + +julia> add_edge!(g, 2, 3); + +julia> add_edge!(g, 3, 1); + +julia> indegree(g) +3-element Array{Int64,1}: + 1 + 0 + 1 +``` +""" +indegree(g::AbstractGraph, v::Integer) = length(inneighbors(g, v)) +indegree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [indegree(g, x) for x in v] + +""" + outdegree(g[, v]) + +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`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = DiGraph(3); + +julia> add_edge!(g, 2, 3); + +julia> add_edge!(g, 3, 1); + +julia> outdegree(g) +3-element Array{Int64,1}: + 0 + 1 + 1 +``` +""" +outdegree(g::AbstractGraph, v::Integer) = length(outneighbors(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. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = DiGraph(3); + +julia> add_edge!(g, 2, 3); + +julia> add_edge!(g, 3, 1); + +julia> degree(g) +3-element Array{Int64,1}: + 1 + 1 + 2 +``` +""" +function degree end +@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] + +""" + has_self_loops(g) + +Return true if `g` has any self loops. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> has_self_loops(g) +false + +julia> add_edge!(g, 1, 1); + +julia> has_self_loops(g) +true +``` +""" +has_self_loops(g::AbstractGraph) = nv(g) == 0 ? false : any(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]) + +""" + weights(g) + +Return the weights of the edges of a graph `g` as a matrix. Defaults +to [`Graphs.DefaultDistance`](@ref). + +### Implementation Notes +In general, referencing the weight of a nonexistent edge is undefined behavior. Do not rely on the `weights` matrix +as a substitute for the graph's [`adjacency_matrix`](@ref). +""" +weights(g::AbstractGraph) = DefaultDistance(nv(g)) \ No newline at end of file diff --git a/src/interface.jl b/src/interface.jl new file mode 100644 index 0000000..31f2145 --- /dev/null +++ b/src/interface.jl @@ -0,0 +1,566 @@ +# This file contains the common interface for Graphs. + +""" + NotImplementedError{M}(m) + +`Exception` thrown when a method from the `AbstractGraph` interface +is not implemented by a given graph type. +""" +struct NotImplementedError{M} <: Exception + m::M + NotImplementedError(m::M) where {M} = new{M}(m) +end + +Base.showerror(io::IO, ie::NotImplementedError) = print(io, "method $(ie.m) not implemented.") + +_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{V, U} end +# abstract type AbstractWeightedEdge{V, U} <: AbstractEdge{V} end + +""" + AbstractEdgeIter + +An abstract type representing an edge iterator. +""" +abstract type AbstractEdgeIter end + +""" + AbstractGraph + +An abstract type representing a multi-graph. +- `V` : Vertex type +- `E` : Edge type + +""" +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 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) + +Return the source vertex of edge `e`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> src(first(edges(g))) +1 +``` +""" +src(e::AbstractEdge) = _NI("src") + +""" + dst(e) + +Return the destination vertex of edge `e`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> dst(first(edges(g))) +2 +``` +""" +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") + +""" + reverse(e) + +Create a new edge from `e` with source and destination vertices reversed. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleDiGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> reverse(first(edges(g))) +Edge 2 => 1 +``` +""" +reverse(e::AbstractEdge) = _NI("reverse") + + +# +# Interface for AbstractGraphs +# +""" + edgetype(g) + +Return the type of graph `g`'s edge +""" +edgetype(g::AbstractGraph{V, E}) where {V, E} = E + +""" + eltype(g) + +Return the type of the graph's vertices +""" +eltype(g::AbstractGraph{V, E}) where {V, E} = V + + +""" + 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`. + +# Examples +```jldoctest +julia> using Graphs + +julia> collect(vertices(SimpleGraph(4))) +4-element Array{Int64,1}: + 1 + 2 + 3 + 4 +``` +""" +vertices(g::AbstractGraph) = _NI("vertices") + +""" + get_edges(g, u, v) + +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 +julia> using Graphs + +julia> g = path_graph(3); + +julia> collect(get_edges(g, 1, 2)) +1-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: + Edge 1 => 2 +``` +""" +@traitfn get_edges(g::AbstractGraph, u::V, v::V) where {V; AbstractVertex{V}} = _NI("get_edges") + +""" + edges(g) + +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 +is invalidated by changes to `g`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = path_graph(3); + +julia> collect(edges(g)) +2-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: + Edge 1 => 2 + Edge 2 => 3 +``` +""" +edges(g::AbstractGraph) = _NI("edges") + +""" + outedges(g, u) + +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 +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 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) + +Return `true` if the graph type `G` is a directed graph; `false` otherwise. +New graph types must implement `is_directed(::Type{<:G})`. +The method can also be called with `is_directed(g::G)` +# Examples +```jldoctest +julia> using Graphs + +julia> is_directed(SimpleGraph(2)) +false + +julia> is_directed(SimpleGraph) +false + +julia> is_directed(SimpleDiGraph(2)) +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) + +Return true if `v` is a vertex of `g`. + +# Examples +```jldoctest +julia> using Graphs + +julia> has_vertex(SimpleGraph(2), 1) +true + +julia> has_vertex(SimpleGraph(2), 3) +false +``` +""" +has_vertex(g, v) = _NI("has_vertex") + +""" + has_edge(g, s, d) + +Return true if the graph `g` has an edge from node `s` to node `d`. + +An optional `has_edge(g, e)` can be implemented to check if an edge belongs +to a graph, including any data other than source and destination node. + +`e ∈ edges(g)` or `e ∈ edges(g)` evaluate as +calls to `has_edge`, c.f. [`edges`](@ref). + +# Examples +```jldoctest +julia> using Graphs + +julia> g = SimpleDiGraph(2); + +julia> add_edge!(g, 1, 2); + +julia> has_edge(g, 1, 2) +true + +julia> has_edge(g, 2, 1) +false +``` +""" +has_edge(g, s, d) = _NI("has_edge") +has_edge(g, e) = has_edge(g, src(e), dst(e)) + +""" + inneighbors(g, v) + +Return a list of all neighbors connected to vertex `v` by an incoming edge. + +### 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> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); + +julia> inneighbors(g, 4) +2-element Array{Int64,1}: + 3 + 5 +``` +""" +inneighbors(g, v) = _NI("inneighbors") + +""" + outneighbors(g, v) + +Return a list of all neighbors connected to vertex `v` by an outgoing edge. + +# 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> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); + +julia> outneighbors(g, 4) +1-element Array{Int64,1}: + 5 +``` +""" +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) + +Return a zero-vertex, zero-edge version of the graph type `G`. +The fallback is defined for graph values `zero(g::G) = zero(G)`. + +# Examples +```jldoctest +julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); + +julia> zero(typeof(g)) +{0, 0} directed simple Int64 graph + +julia> zero(g) +{0, 0} directed simple Int64 graph +``` +""" +zero(::Type{<:AbstractGraph}) = _NI("zero") + +zero(g::G) where {G<: AbstractGraph} = zero(G) \ No newline at end of file diff --git a/src/utils.jl b/src/utils.jl new file mode 100644 index 0000000..a8ea62c --- /dev/null +++ b/src/utils.jl @@ -0,0 +1,73 @@ +""" + 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 vertices(g) + funci = f(g, i) + if comparison(funci, value) + value = funci + end + end + return value +end + +# """ +# insorted(item, collection) + +# Return true if `item` is in sorted collection `collection`. + +# ### Implementation Notes +# Does not verify that `collection` is sorted. +# """ +# function insorted(item, collection) +# index = searchsortedfirst(collection, item) +# @inbounds return (index <= length(collection) && collection[index] == item) +# end + +# """ +# findall!(A, B) + +# Set the `B[1:|I|]` to `I` where `I` is the set of indices `A[I]` returns true. + +# Assumes `length(B) >= |I|`. +# """ +# function findall!(A::Union{BitArray{1},Vector{Bool}}, B::Vector{T}) where {T<:Integer} +# len = 0 +# @inbounds for (i, a) in enumerate(A) +# if a +# len += 1 +# B[len] = i +# end +# end +# return B +# end + +""" + deepcopy_adjlist(adjlist::Vector{Vector{T}}) + +Internal utility function for copying adjacency lists. +On adjacency lists this function is more efficient than `deepcopy` for two reasons: +- As of Julia v1.0.2, `deepcopy` is not typestable. +- `deepcopy` needs to track all references when traversing a recursive data structure + in order to ensure that references to the same location do need get assigned to + different locations in the copy. Because we can assume that all lists in our + adjacency list are different, we don't need to keep track of them. +If `T` is not a bitstype (e.g. `BigInt`), we use the standard `deepcopy`. +""" +function deepcopy_adjlist(adjlist::Vector{Vector{T}}) where {T} + isbitstype(T) || return deepcopy(adjlist) + + result = Vector{Vector{T}}(undef, length(adjlist)) + @inbounds for (i, list) in enumerate(adjlist) + result_list = Vector{T}(undef, length(list)) + for (j, item) in enumerate(list) + result_list[j] = item + end + result[i] = result_list + end + + return result +end \ No newline at end of file From 7aca55719a25f07cac3fb2c3781d558098af171d Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Fri, 1 Sep 2023 00:26:08 +0200 Subject: [PATCH 2/8] appeasing Aqua --- Project.toml | 1 + docs/make.jl | 1 + src/GraphsBase.jl | 2 +- src/interface.jl | 15 +++++++++------ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 439ec6f..10929ce 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] +SimpleTraits = "0.9" julia = "1.6" [extras] diff --git a/docs/make.jl b/docs/make.jl index 9c00dbc..ac3860c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,4 +1,5 @@ using GraphsBase +using Graphs using Documenter DocMeta.setdocmeta!(GraphsBase, :DocTestSetup, :(using GraphsBase); recursive=true) diff --git a/src/GraphsBase.jl b/src/GraphsBase.jl index 57a60d4..bc85b69 100644 --- a/src/GraphsBase.jl +++ b/src/GraphsBase.jl @@ -10,7 +10,7 @@ using SimpleTraits export # Interface -AbstractVertex, is_vertex, AbstractEdge, AbstractWeightedEdge, AbstractEdgeIter, +AbstractVertex, is_vertex, AbstractEdge, AbstractEdgeIter, AbstractGraph, vertices, edges, edgetype, nv, ne, src, dst, is_directed, IsDirected, is_range_based, IsRangeBased, is_simply_mutable, IsSimplyMutable, is_mutable, IsMutable, is_weight_mutable, IsWeightMutable, is_vertex_stable, IsVertexStable, diff --git a/src/interface.jl b/src/interface.jl index 31f2145..64d6ca5 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -70,16 +70,19 @@ abstract type AbstractBidirectionalGraph{V, E} <: AbstractGraph{V, E} end @traitdef IsVertexStable{G<:AbstractGraph} @traitimpl IsVertexStable{G} <- is_vertex_stable(G) + +# TODO: We can't define isless because it is type piracy. +# We should probably document that it needs isless and == to be implemented # # Interface for AbstractVertex # -import Base.isless#, Base.:(==) -""" - isless(v1, v2) +# import Base.isless +# """ +# 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") +# 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("==") From dae585d91b8149b28d6d90b30103838ffe8da82f Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Fri, 1 Sep 2023 00:32:34 +0200 Subject: [PATCH 3/8] add Graphs in dependencies --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 10929ce..6718d29 100644 --- a/Project.toml +++ b/Project.toml @@ -14,6 +14,7 @@ julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From dd4c6b06df77b1ce16b1b211af64be9dab81bcc6 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:14:11 +0200 Subject: [PATCH 4/8] Format --- src/GraphsBase.jl | 79 +++++++++++++++++++++------- src/SimpleGraphs/SimpleGraphs.jl | 86 +++++++++++++++++++++---------- src/SimpleGraphs/simpledigraph.jl | 3 +- src/SimpleGraphs/simpleedge.jl | 4 +- src/core.jl | 18 +++---- src/interface.jl | 60 +++++++++++---------- src/utils.jl | 2 +- 7 files changed, 162 insertions(+), 90 deletions(-) diff --git a/src/GraphsBase.jl b/src/GraphsBase.jl index bc85b69..c695e5e 100644 --- a/src/GraphsBase.jl +++ b/src/GraphsBase.jl @@ -2,29 +2,72 @@ module GraphsBase using SimpleTraits - # import Base: adjoint, write, ==, <, *, ≈, convert, isless, issubset, # reverse, reverse!, isassigned, getindex, setindex!, show, # print, copy, in, sum, size, eltype, length, ndims, transpose, # iterate, eltype, get, Pair, Tuple, zero export -# Interface -AbstractVertex, is_vertex, AbstractEdge, AbstractEdgeIter, -AbstractGraph, vertices, edges, edgetype, nv, ne, src, dst, -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, -Edge, Graph, SimpleGraph, SimpleGraphFromIterator, DiGraph, SimpleDiGraphFromIterator, -SimpleDiGraph, - -# core -is_ordered, add_vertices!, indegree, outdegree, degree, -neighbors, all_neighbors, has_self_loops, weights, - -# simplegraphs -add_edge!, add_vertex!, add_vertices!, rem_edge!, rem_vertex!, rem_vertices! + # Interface + AbstractVertex, + is_vertex, + AbstractEdge, + AbstractEdgeIter, + AbstractGraph, + vertices, + edges, + edgetype, + nv, + ne, + src, + dst, + 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, + Edge, + Graph, + SimpleGraph, + SimpleGraphFromIterator, + DiGraph, + SimpleDiGraphFromIterator, + SimpleDiGraph, + + # core + is_ordered, + add_vertices!, + indegree, + outdegree, + degree, + neighbors, + all_neighbors, + has_self_loops, + weights, + + # simplegraphs + add_edge!, + add_vertex!, + add_vertices!, + rem_edge!, + rem_vertex!, + rem_vertices! """ GraphsBase @@ -75,4 +118,4 @@ a `Graph` or `DiGraph`. """ const Edge = GraphsBase.SimpleGraphs.SimpleEdge -end # module \ No newline at end of file +end # module diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index 841f188..b0ec661 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -9,20 +9,52 @@ import Base: eltype, show, ==, Pair, Tuple, copy, length, issubset, reverse, zero, in, iterate import GraphsBase: - _NI, AbstractGraph, AbstractEdge, AbstractEdgeIter, - 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, + _NI, + AbstractGraph, + AbstractEdge, + AbstractEdgeIter, + 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, insorted -export AbstractSimpleGraph, AbstractSimpleEdge, - SimpleEdge, SimpleGraph, SimpleGraphFromIterator, SimpleGraphEdge, - SimpleDiGraph, SimpleDiGraphFromIterator, SimpleDiGraphEdge, - add_vertex!, add_edge!, rem_vertex!, rem_vertices!, rem_edge! - -abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T, Int} end +export AbstractSimpleGraph, + AbstractSimpleEdge, + SimpleEdge, + SimpleGraph, + SimpleGraphFromIterator, + SimpleGraphEdge, + SimpleDiGraph, + SimpleDiGraphFromIterator, + SimpleDiGraphEdge, + add_vertex!, + add_edge!, + rem_vertex!, + rem_vertices!, + rem_edge! + +abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T,Int} end """ AbstractSimpleGraph @@ -33,14 +65,14 @@ An abstract type representing a simple graph structure. - `fadjlist::Vector{Vector{Integer}}` - `ne::Integer` """ -abstract type AbstractSimpleGraph{T<:Integer} <: AbstractGraph{T, AbstractSimpleEdge{T}} end +abstract type AbstractSimpleGraph{T<:Integer} <: AbstractGraph{T,AbstractSimpleEdge{T}} end -function show(io::IO, ::MIME"text/plain", g::AbstractSimpleGraph{T}) where T +function show(io::IO, ::MIME"text/plain", g::AbstractSimpleGraph{T}) where {T} dir = is_directed(g) ? "directed" : "undirected" - print(io, "{$(nv(g)), $(ne(g))} $dir simple $T graph") + return print(io, "{$(nv(g)), $(ne(g))} $dir simple $T graph") end -nv(g::AbstractSimpleGraph{T}) where T = T(length(fadj(g))) +nv(g::AbstractSimpleGraph{T}) where {T} = T(length(fadj(g))) vertices(g::AbstractSimpleGraph) = Base.OneTo(nv(g)) """ @@ -57,24 +89,22 @@ function throw_if_invalid_eltype(T::Type{<:Integer}) end end - edges(g::AbstractSimpleGraph) = SimpleEdgeIter(g) - fadj(g::AbstractSimpleGraph) = g.fadjlist fadj(g::AbstractSimpleGraph, v::Integer) = g.fadjlist[v] - 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 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)] + !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 @@ -89,7 +119,7 @@ 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 +function issubset(g::T, h::T) where {T<:AbstractSimpleGraph} nv(g) <= nv(h) || return false for u in vertices(g) u_nbrs_g = neighbors(g, u) @@ -98,7 +128,7 @@ function issubset(g::T, h::T) where T <: AbstractSimpleGraph u_nbrs_h = neighbors(h, u) p = 1 len_u_nbrs_g > length(u_nbrs_h) && return false - (u_nbrs_g[1] < u_nbrs_h[1] || u_nbrs_g[end] > u_nbrs_h[end]) && return false + (u_nbrs_g[1] < u_nbrs_h[1] || u_nbrs_g[end] > u_nbrs_h[end]) && return false @inbounds for v in u_nbrs_h if v == u_nbrs_g[p] p == len_u_nbrs_g && break @@ -114,8 +144,8 @@ has_vertex(g::AbstractSimpleGraph, v::Integer) = v in vertices(g) ne(g::AbstractSimpleGraph) = g.ne -function rem_edge!(g::AbstractSimpleGraph{T}, u::Integer, v::Integer) where T - rem_edge!(g, edgetype(g)(T(u), T(v))) +function rem_edge!(g::AbstractSimpleGraph{T}, u::Integer, v::Integer) where {T} + return rem_edge!(g, edgetype(g)(T(u), T(v))) end """ @@ -213,4 +243,4 @@ include("./simpledigraph.jl") include("./simplegraph.jl") include("./simpleedgeiter.jl") -end # module \ No newline at end of file +end # module diff --git a/src/SimpleGraphs/simpledigraph.jl b/src/SimpleGraphs/simpledigraph.jl index f8734bd..0a125b2 100644 --- a/src/SimpleGraphs/simpledigraph.jl +++ b/src/SimpleGraphs/simpledigraph.jl @@ -24,7 +24,6 @@ function SimpleDiGraph( return SimpleDiGraph{T}(ne, fadjlist, badjlist) end - # DiGraph{UInt8}(6), DiGraph{Int16}(7), DiGraph{Int8}() """ SimpleDiGraph{T}(n=0) @@ -179,7 +178,7 @@ julia> SimpleDiGraph(g) """ function SimpleDiGraph(g::AbstractSimpleGraph) h = SimpleDiGraph(nv(g)) - num_self_loops = sum(v -> has_edge(g, v, v), vertices(g); init = 0) + num_self_loops = sum(v -> has_edge(g, v, v), vertices(g); init=0) h.ne = ne(g) * 2 - num_self_loops h.fadjlist = deepcopy_adjlist(fadj(g)) h.badjlist = deepcopy_adjlist(badj(g)) diff --git a/src/SimpleGraphs/simpleedge.jl b/src/SimpleGraphs/simpleedge.jl index d09bf67..c07943e 100644 --- a/src/SimpleGraphs/simpleedge.jl +++ b/src/SimpleGraphs/simpleedge.jl @@ -32,4 +32,6 @@ function ==(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) return (src(e1) == src(e2) && dst(e1) == dst(e2)) end hash(e::AbstractSimpleEdge, h::UInt) = hash(src(e), hash(dst(e), h)) -isless(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) = (src(e1) < src(e2)) || ((src(e1) == src(e2)) && (dst(e1) < dst(e2))) +function isless(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) + return (src(e1) < src(e2)) || ((src(e1) == src(e2)) && (dst(e1) < dst(e2))) +end diff --git a/src/core.jl b/src/core.jl index 709a273..778daa0 100644 --- a/src/core.jl +++ b/src/core.jl @@ -25,7 +25,6 @@ false """ is_ordered(e::AbstractEdge) = src(e) <= dst(e) - """ neighbors(g, v) @@ -101,8 +100,7 @@ julia> all_neighbors(g, 3) 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) +@traitfn all_neighbors(g::::(!IsDirected), v::Integer) = neighbors(g, v) """ indegree(g[, v]) @@ -128,7 +126,7 @@ julia> indegree(g) ``` """ indegree(g::AbstractGraph, v::Integer) = length(inneighbors(g, v)) -indegree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [indegree(g, x) for x in v] +indegree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [indegree(g, x) for x in v] """ outdegree(g[, v]) @@ -154,7 +152,7 @@ julia> outdegree(g) ``` """ outdegree(g::AbstractGraph, v::Integer) = length(outneighbors(g, v)) -outdegree(g::AbstractGraph, v::AbstractVector = vertices(g)) = [outdegree(g, x) for x in v] +outdegree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [outdegree(g, x) for x in v] """ degree(g[, v]) @@ -185,7 +183,7 @@ function degree end @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] +degree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [degree(g, x) for x in v] """ has_self_loops(g) @@ -209,7 +207,8 @@ julia> has_self_loops(g) true ``` """ -has_self_loops(g::AbstractGraph) = nv(g) == 0 ? false : any(v -> has_edge(g, v, v), vertices(g)) +has_self_loops(g::AbstractGraph) = + nv(g) == 0 ? false : any(v -> has_edge(g, v, v), vertices(g)) """ add_vertices!(g, n) @@ -228,7 +227,8 @@ julia> add_vertices!(g, 2) 2 ``` """ -@traitfn add_vertices!(g::AbstractGraph::IsSimplyMutable, n::Integer) = sum([add_vertex!(g) for i = 1:n]) +@traitfn add_vertices!(g::AbstractGraph::IsSimplyMutable, n::Integer) = + sum([add_vertex!(g) for i in 1:n]) """ weights(g) @@ -240,4 +240,4 @@ to [`Graphs.DefaultDistance`](@ref). In general, referencing the weight of a nonexistent edge is undefined behavior. Do not rely on the `weights` matrix as a substitute for the graph's [`adjacency_matrix`](@ref). """ -weights(g::AbstractGraph) = DefaultDistance(nv(g)) \ No newline at end of file +weights(g::AbstractGraph) = DefaultDistance(nv(g)) diff --git a/src/interface.jl b/src/interface.jl index 64d6ca5..11275e6 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -11,7 +11,9 @@ struct NotImplementedError{M} <: Exception NotImplementedError(m::M) where {M} = new{M}(m) end -Base.showerror(io::IO, ie::NotImplementedError) = print(io, "method $(ie.m) not implemented.") +function Base.showerror(io::IO, ie::NotImplementedError) + return print(io, "method $(ie.m) not implemented.") +end _NI(m) = throw(NotImplementedError(m)) @@ -21,7 +23,7 @@ _NI(m) = throw(NotImplementedError(m)) A trait representing a single vertex. """ @traitdef AbstractVertex{V} -@traitimpl AbstractVertex{V} <- is_vertex(V) +@traitimpl AbstractVertex{V} < -is_vertex(V) """ AbstractEdge @@ -30,7 +32,7 @@ An abstract type representing a single edge between two vertices of a graph. - `V`: Vertex type - `U`: Weight type """ -abstract type AbstractEdge{V, U} end +abstract type AbstractEdge{V,U} end # abstract type AbstractWeightedEdge{V, U} <: AbstractEdge{V} end """ @@ -48,28 +50,27 @@ An abstract type representing a multi-graph. - `E` : Edge type """ -abstract type AbstractGraph{V, E<:AbstractEdge{V}} end +abstract type AbstractGraph{V,E<:AbstractEdge{V}} end -abstract type AbstractBidirectionalGraph{V, E} <: AbstractGraph{V, E} end +abstract type AbstractBidirectionalGraph{V,E} <: AbstractGraph{V,E} end @traitdef IsDirected{G<:AbstractGraph} -@traitimpl IsDirected{G} <- is_directed(G) +@traitimpl IsDirected{G} < -is_directed(G) @traitdef IsRangeBased{G<:AbstractGraph} -@traitimpl IsRangeBased{G} <- is_range_based(G) +@traitimpl IsRangeBased{G} < -is_range_based(G) @traitdef IsSimplyMutable{G<:AbstractGraph} -@traitimpl IsSimplyMutable{G} <- is_simply_mutable(G) +@traitimpl IsSimplyMutable{G} < -is_simply_mutable(G) @traitdef IsMutable{G<:AbstractGraph} -@traitimpl IsMutable{G} <- is_mutable(G) +@traitimpl IsMutable{G} < -is_mutable(G) @traitdef IsWeightMutable{G<:AbstractGraph} -@traitimpl IsWeightMutable{G} <- is_weight_mutable(G) +@traitimpl IsWeightMutable{G} < -is_weight_mutable(G) @traitdef IsVertexStable{G<:AbstractGraph} -@traitimpl IsVertexStable{G} <- is_vertex_stable(G) - +@traitimpl IsVertexStable{G} < -is_vertex_stable(G) # TODO: We can't define isless because it is type piracy. # We should probably document that it needs isless and == to be implemented @@ -103,7 +104,7 @@ hash(v::AbstractEdge) = _NI("hash") Return true if edge e1 is less than edge e2 in lexicographic order. """ -isless(v1::AbstractEdge , v2::AbstractEdge) = _NI("src") +isless(v1::AbstractEdge, v2::AbstractEdge) = _NI("src") ==(e1::AbstractEdge, e2::AbstractEdge) = _NI("==") @@ -150,8 +151,7 @@ dst(e::AbstractEdge) = _NI("dst") Return the weight of edge `e`. """ -weight(e::AbstractEdge{V, U}) where {V, U} = one(U) - +weight(e::AbstractEdge{V,U}) where {V,U} = one(U) Pair(e::AbstractEdge) = _NI("Pair") Tuple(e::AbstractEdge) = _NI("Tuple") @@ -175,7 +175,6 @@ Edge 2 => 1 """ reverse(e::AbstractEdge) = _NI("reverse") - # # Interface for AbstractGraphs # @@ -184,15 +183,14 @@ reverse(e::AbstractEdge) = _NI("reverse") Return the type of graph `g`'s edge """ -edgetype(g::AbstractGraph{V, E}) where {V, E} = E +edgetype(g::AbstractGraph{V,E}) where {V,E} = E """ eltype(g) Return the type of the graph's vertices """ -eltype(g::AbstractGraph{V, E}) where {V, E} = V - +eltype(g::AbstractGraph{V,E}) where {V,E} = V """ vertices(g) @@ -238,7 +236,8 @@ julia> collect(get_edges(g, 1, 2)) Edge 1 => 2 ``` """ -@traitfn get_edges(g::AbstractGraph, u::V, v::V) where {V; AbstractVertex{V}} = _NI("get_edges") +@traitfn get_edges(g::AbstractGraph, u::V, v::V) where {V; AbstractVertex{V}} = + _NI("get_edges") """ edges(g) @@ -344,7 +343,6 @@ julia> ne(g) """ ne(g::AbstractGraph) = length(edges(g)) - """ is_vertex(G) @@ -352,7 +350,7 @@ 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{T}) where {T} = _NI("is_vertex") is_vertex(::Type{<:Integer}) = true """ @@ -376,7 +374,7 @@ true ``` """ is_directed(::G) where {G} = is_directed(G) -is_directed(::Type{T}) where T = _NI("is_directed") +is_directed(::Type{T}) where {T} = _NI("is_directed") """ is_range_based(G) @@ -386,7 +384,7 @@ 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_range_based(::Type{T}) where {T} = false """ is_simply_mutable(G) @@ -397,7 +395,7 @@ 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_simply_mutable(::Type{T}) where {T} = false """ is_mutable(G) @@ -408,7 +406,7 @@ 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_mutable(::Type{T}) where {T} = false """ is_weight_mutable(G) @@ -419,7 +417,7 @@ 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_weight_mutable(::Type{T}) where {T} = false """ is_vertex_stable(G) @@ -430,7 +428,7 @@ 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 +is_vertex_stable(::Type{T}) where {T} = false """ has_vertex(g, v) @@ -538,14 +536,14 @@ julia> length(c) 5 ``` """ -get_vertex_container(g::AbstractGraph{V}, K::Type) where V = Dict{V, K}() +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}() +get_edge_container(g::AbstractGraph{V,E}, K::Type) where {V,E} = Dict{E,K}() """ zero(G) @@ -566,4 +564,4 @@ julia> zero(g) """ zero(::Type{<:AbstractGraph}) = _NI("zero") -zero(g::G) where {G<: AbstractGraph} = zero(G) \ No newline at end of file +zero(g::G) where {G<:AbstractGraph} = zero(G) diff --git a/src/utils.jl b/src/utils.jl index a8ea62c..7bde964 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -70,4 +70,4 @@ function deepcopy_adjlist(adjlist::Vector{Vector{T}}) where {T} end return result -end \ No newline at end of file +end From 3e9e4af39e52f0bce725d2835649d0196ccad40c Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:50:30 +0200 Subject: [PATCH 5/8] Interface as simple as possible, with docs --- .github/workflows/DocPreviewCleanup.yml | 28 + .gitignore | 2 +- Project.toml | 3 +- README.md | 3 - docs/Manifest.toml | 254 +++++++ docs/make.jl | 13 +- docs/src/implementations.md | 18 + docs/src/index.md | 9 +- docs/src/interface.md | 66 ++ src/GraphsBase.jl | 142 +--- src/SimpleGraphs/SimpleGraphs.jl | 246 +------ src/SimpleGraphs/simpledigraph.jl | 626 +--------------- src/SimpleGraphs/simpleedge.jl | 49 +- src/SimpleGraphs/simpleedgeiter.jl | 123 ---- src/SimpleGraphs/simplegraph.jl | 687 +----------------- .../SimpleWeightedGraphs.jl | 15 + .../simpleweighteddigraph.jl | 27 + .../simpleweightededge.jl | 26 + .../simpleweightedgraph.jl | 23 + src/core.jl | 243 ------- src/interface.jl | 567 --------------- src/interface/abstractedge.jl | 98 +++ src/interface/abstractgraph.jl | 108 +++ src/interface/modification.jl | 27 + src/interface/optional.jl | 96 +++ src/interface/utils.jl | 25 + src/utils.jl | 73 -- test/SimpleGraphs/SimpleGraphs.jl | 6 + .../SimpleWeightedGraphs.jl | 6 + test/interface/abstractedge.jl | 6 + test/interface/abstractgraph.jl | 5 + test/interface/utils.jl | 7 + test/runtests.jl | 18 +- 33 files changed, 957 insertions(+), 2688 deletions(-) create mode 100644 .github/workflows/DocPreviewCleanup.yml create mode 100644 docs/Manifest.toml create mode 100644 docs/src/implementations.md create mode 100644 docs/src/interface.md delete mode 100644 src/SimpleGraphs/simpleedgeiter.jl create mode 100644 src/SimpleWeightedGraphs/SimpleWeightedGraphs.jl create mode 100644 src/SimpleWeightedGraphs/simpleweighteddigraph.jl create mode 100644 src/SimpleWeightedGraphs/simpleweightededge.jl create mode 100644 src/SimpleWeightedGraphs/simpleweightedgraph.jl delete mode 100644 src/core.jl delete mode 100644 src/interface.jl create mode 100644 src/interface/abstractedge.jl create mode 100644 src/interface/abstractgraph.jl create mode 100644 src/interface/modification.jl create mode 100644 src/interface/optional.jl create mode 100644 src/interface/utils.jl delete mode 100644 src/utils.jl create mode 100644 test/SimpleGraphs/SimpleGraphs.jl create mode 100644 test/SimpleWeightedGraphs/SimpleWeightedGraphs.jl create mode 100644 test/interface/abstractedge.jl create mode 100644 test/interface/abstractgraph.jl create mode 100644 test/interface/utils.jl diff --git a/.github/workflows/DocPreviewCleanup.yml b/.github/workflows/DocPreviewCleanup.yml new file mode 100644 index 0000000..365d56f --- /dev/null +++ b/.github/workflows/DocPreviewCleanup.yml @@ -0,0 +1,28 @@ +name: Doc Preview Cleanup + +on: + pull_request: + types: [closed] + +jobs: + doc-preview-cleanup: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v3 + with: + ref: gh-pages + - name: Delete preview and history + push changes + run: | + if [ -d "previews/PR$PRNUM" ]; then + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "previews/PR$PRNUM" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + git push --force origin gh-pages-new:gh-pages + fi + env: + PRNUM: ${{ github.event.number }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95731a5..96b9f5b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ *.jl.cov *.jl.mem /Manifest.toml -/docs/Manifest.toml +# /docs/Manifest.toml /docs/build/ diff --git a/Project.toml b/Project.toml index 6718d29..2e2d8fc 100644 --- a/Project.toml +++ b/Project.toml @@ -4,17 +4,18 @@ authors = ["JuliaGraphs contributors"] version = "0.1.0-DEV" [deps] +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] +DocStringExtensions = "0.9" SimpleTraits = "0.9" julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/README.md b/README.md index 17770e7..fae50db 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ # GraphsBase - [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaGraphs.github.io/GraphsBase.jl/dev/) [![Build Status](https://github.com/JuliaGraphs/GraphsBase.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaGraphs/GraphsBase.jl/actions/workflows/CI.yml?query=branch%3Amain) [![Coverage](https://codecov.io/gh/JuliaGraphs/GraphsBase.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaGraphs/GraphsBase.jl) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) [![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) - Basic interface and structures for the JuliaGraphs ecosystem diff --git a/docs/Manifest.toml b/docs/Manifest.toml new file mode 100644 index 0000000..ff98fca --- /dev/null +++ b/docs/Manifest.toml @@ -0,0 +1,254 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.3" +manifest_format = "2.0" +project_hash = "4ab590d1fea183f22c4d344fdfc560e0145fca75" + +[[deps.ANSIColoredPrinters]] +git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" +uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" +version = "0.0.1" + +[[deps.AbstractTrees]] +git-tree-sha1 = "faa260e4cb5aba097a73fab382dd4b5819d8ec8c" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.4" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.0.5+0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Documenter]] +deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "Dates", "DocStringExtensions", "Downloads", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "Test", "Unicode"] +git-tree-sha1 = "f667b805e90d643aeb1ca70189827f991a7cc115" +uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +version = "1.1.0" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.GraphsBase]] +deps = ["DocStringExtensions", "SimpleTraits", "SparseArrays"] +path = ".." +uuid = "ad2ac648-372e-45be-9d57-a550431b71c3" +version = "0.1.0-DEV" + +[[deps.IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.3" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.LazilyInitializedFields]] +git-tree-sha1 = "410fe4739a4b092f2ffe36fcb0dcc3ab12648ce1" +uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" +version = "1.2.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.84.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.10.2+0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "9ee1618cbf5240e6d4e0371d6f24065083f60c48" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.11" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MarkdownAST]] +deps = ["AbstractTrees", "Markdown"] +git-tree-sha1 = "e8513266815200c0c8f522d6d44ffb5e9b366ae4" +uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +version = "0.1.1" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2022.10.11" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.21+4" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.7.2" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.9.2" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.0" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.1" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.RegistryInstances]] +deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] +git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" +uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" +version = "0.1.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "5.10.1+6" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.48.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+0" diff --git a/docs/make.jl b/docs/make.jl index ac3860c..b243896 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,5 +1,4 @@ using GraphsBase -using Graphs using Documenter DocMeta.setdocmeta!(GraphsBase, :DocTestSetup, :(using GraphsBase); recursive=true) @@ -7,15 +6,21 @@ DocMeta.setdocmeta!(GraphsBase, :DocTestSetup, :(using GraphsBase); recursive=tr makedocs(; modules=[GraphsBase], authors="JuliaGraphs contributors", - repo="https://github.com/JuliaGraphs/GraphsBase.jl/blob/{commit}{path}#{line}", sitename="GraphsBase.jl", format=Documenter.HTML(; + repolink="https://github.com/JuliaGraphs/GraphsBase.jl", prettyurls=get(ENV, "CI", "false") == "true", canonical="https://JuliaGraphs.github.io/GraphsBase.jl", edit_link="main", assets=String[], ), - pages=["Home" => "index.md"], + pages=[ + "Home" => "index.md", + "Interface" => "interface.md", + "Implementations" => "implementations.md", + ], ) -deploydocs(; repo="github.com/JuliaGraphs/GraphsBase.jl", devbranch="main") +deploydocs(; + repo="github.com/JuliaGraphs/GraphsBase.jl", devbranch="main", push_preview=true +) diff --git a/docs/src/implementations.md b/docs/src/implementations.md new file mode 100644 index 0000000..0b70c50 --- /dev/null +++ b/docs/src/implementations.md @@ -0,0 +1,18 @@ +# Concrete implementations + +## `SimpleGraphs` + +```@autodocs +Modules = [GraphsBase.SimpleGraphs] +``` + +## `SimpleWeightedGraphs` + +```@autodocs +Modules = [GraphsBase.SimpleWeightedGraphs] +``` + +## Index + +```@index +``` diff --git a/docs/src/index.md b/docs/src/index.md index ac7c56e..469b56d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,11 +4,6 @@ CurrentModule = GraphsBase # GraphsBase -Documentation for [GraphsBase](https://github.com/JuliaGraphs/GraphsBase.jl). - -```@index -``` - -```@autodocs -Modules = [GraphsBase] +```@docs +GraphsBase ``` diff --git a/docs/src/interface.md b/docs/src/interface.md new file mode 100644 index 0000000..928695e --- /dev/null +++ b/docs/src/interface.md @@ -0,0 +1,66 @@ +# Interface + +## Edge + +```@docs +AbstractEdge +src +dst +weight +Base.reverse +``` + +## Graph (required) + +```@docs +AbstractGraph +is_directed +vertices +out_edges +in_edges +``` + +## Graph (optional) + +```@docs +nv +ne +has_vertex +has_edge +has_self_loops +edges +out_neighbors +in_neighbors +create_vertex_container +create_edge_container +``` + +## Graph (modification) + +```@docs +add_vertex! +rm_vertex! +add_edge! +rm_edge! +``` + +## Element types + +```@docs +Base.eltype +edgetype +weighttype +``` + +## Checks + +```@docs +GraphsBase.check_comparable_interface +GraphsBase.check_edge_interface +GraphsBase.check_graph_interface +``` + +## Index + +```@index +``` diff --git a/src/GraphsBase.jl b/src/GraphsBase.jl index c695e5e..a8dce34 100644 --- a/src/GraphsBase.jl +++ b/src/GraphsBase.jl @@ -1,121 +1,37 @@ -module GraphsBase - -using SimpleTraits - -# import Base: adjoint, write, ==, <, *, ≈, convert, isless, issubset, -# reverse, reverse!, isassigned, getindex, setindex!, show, -# print, copy, in, sum, size, eltype, length, ndims, transpose, -# iterate, eltype, get, Pair, Tuple, zero - -export - # Interface - AbstractVertex, - is_vertex, - AbstractEdge, - AbstractEdgeIter, - AbstractGraph, - vertices, - edges, - edgetype, - nv, - ne, - src, - dst, - 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, - Edge, - Graph, - SimpleGraph, - SimpleGraphFromIterator, - DiGraph, - SimpleDiGraphFromIterator, - SimpleDiGraph, - - # core - is_ordered, - add_vertices!, - indegree, - outdegree, - degree, - neighbors, - all_neighbors, - has_self_loops, - weights, - - # simplegraphs - add_edge!, - add_vertex!, - add_vertices!, - rem_edge!, - rem_vertex!, - rem_vertices! - """ GraphsBase -The API for the Graphs ecosystem. - -Simple graphs (not multi- or hypergraphs) are represented in a memory- and -time-efficient manner with adjacency lists and edge sets. Both directed and -undirected graphs are supported via separate types, and conversion is available -from directed to undirected. - -The project goal is to mirror the functionality of robust network and graph -analysis libraries such as NetworkX while being simpler to use and more -efficient than existing Julian graph libraries such as Graphs.jl. It is an -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/Graphs.jl) is available, -and tutorials are available at the -[JuliaGraphsTutorials repository](https://github.com/JuliaGraphs/JuliaGraphsTutorials). +The basic interface and graph types for the JuliaGraphs ecosystem. """ -GraphsBase -include("interface.jl") -include("utils.jl") -include("core.jl") -include("SimpleGraphs/SimpleGraphs.jl") - -using .SimpleGraphs -""" - Graph +module GraphsBase -A datastruture representing an undirected graph. -""" -const Graph = GraphsBase.SimpleGraphs.SimpleGraph -""" - DiGraph +using DocStringExtensions +using SimpleTraits +using SparseArrays + +export AbstractEdge, check_edge_interface +export eltype, weighttype +export src, dst, weight + +export AbstractGraph, check_graph_interface +export edgetype +export is_directed +export vertices +export edges, out_edges, in_edges +export nv, ne +export has_vertex, has_edge, has_self_loops +export out_neighbors, in_neighbors +export create_vertex_container, create_edge_container +export add_vertex!, rm_vertex!, add_edge!, rm_edge! + +include("interface/utils.jl") +include("interface/abstractedge.jl") +include("interface/abstractgraph.jl") +include("interface/optional.jl") +include("interface/modification.jl") -A datastruture representing a directed graph. -""" -const DiGraph = GraphsBase.SimpleGraphs.SimpleDiGraph -""" - Edge +include("SimpleGraphs/SimpleGraphs.jl") -A datastruture representing an edge between two vertices in -a `Graph` or `DiGraph`. -""" -const Edge = GraphsBase.SimpleGraphs.SimpleEdge +include("SimpleWeightedGraphs/SimpleWeightedGraphs.jl") -end # module +end diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index b0ec661..b421894 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -1,246 +1,14 @@ module SimpleGraphs -using SparseArrays -# using LinearAlgebra +using DocStringExtensions using GraphsBase -using SimpleTraits -import Base: - eltype, show, ==, Pair, Tuple, copy, length, issubset, reverse, zero, in, iterate +export SimpleEdge +export SimpleGraph +export SimpleDiGraph -import GraphsBase: - _NI, - AbstractGraph, - AbstractEdge, - AbstractEdgeIter, - 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, - insorted +include("simpleedge.jl") +include("simplegraph.jl") +include("simpledigraph.jl") -export AbstractSimpleGraph, - AbstractSimpleEdge, - SimpleEdge, - SimpleGraph, - SimpleGraphFromIterator, - SimpleGraphEdge, - SimpleDiGraph, - SimpleDiGraphFromIterator, - SimpleDiGraphEdge, - add_vertex!, - add_edge!, - rem_vertex!, - rem_vertices!, - rem_edge! - -abstract type AbstractSimpleEdge{T<:Integer} <: AbstractEdge{T,Int} end - -""" - AbstractSimpleGraph - -An abstract type representing a simple graph structure. -`AbstractSimpleGraph`s must have the following elements: - - `vertices::UnitRange{Integer}` - - `fadjlist::Vector{Vector{Integer}}` - - `ne::Integer` -""" -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" - return print(io, "{$(nv(g)), $(ne(g))} $dir simple $T graph") -end - -nv(g::AbstractSimpleGraph{T}) where {T} = T(length(fadj(g))) -vertices(g::AbstractSimpleGraph) = Base.OneTo(nv(g)) - -""" - throw_if_invalid_eltype(T) - -Internal function, throw a `DomainError` if `T` is not a concrete type `Integer`. -Can be used in the constructor of AbstractSimpleGraphs, -as Julia's typesystem does not enforce concrete types, which can lead to -problems. E.g `SimpleGraph{Signed}`. -""" -function throw_if_invalid_eltype(T::Type{<:Integer}) - if !isconcretetype(T) - throw(DomainError(T, "Eltype for AbstractSimpleGraph must be concrete type.")) - end end - -edges(g::AbstractSimpleGraph) = SimpleEdgeIter(g) - -fadj(g::AbstractSimpleGraph) = g.fadjlist -fadj(g::AbstractSimpleGraph, v::Integer) = g.fadjlist[v] - -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)) -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 - for u in vertices(g) - u_nbrs_g = neighbors(g, u) - len_u_nbrs_g = length(u_nbrs_g) - len_u_nbrs_g == 0 && continue - u_nbrs_h = neighbors(h, u) - p = 1 - len_u_nbrs_g > length(u_nbrs_h) && return false - (u_nbrs_g[1] < u_nbrs_h[1] || u_nbrs_g[end] > u_nbrs_h[end]) && return false - @inbounds for v in u_nbrs_h - if v == u_nbrs_g[p] - p == len_u_nbrs_g && break - p += 1 - end - end - p == len_u_nbrs_g || return false - end - return true -end - -has_vertex(g::AbstractSimpleGraph, v::Integer) = v in vertices(g) - -ne(g::AbstractSimpleGraph) = g.ne - -function rem_edge!(g::AbstractSimpleGraph{T}, u::Integer, v::Integer) where {T} - return rem_edge!(g, edgetype(g)(T(u), T(v))) -end - -""" - 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``. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(2); - -julia> rem_vertex!(g, 2) -true - -julia> rem_vertex!(g, 2) -false -``` -""" -function rem_vertex!(g::AbstractSimpleGraph, v::Integer) - v in vertices(g) || return false - n = nv(g) - self_loop_n = false # true if n is self-looped (see #820) - - # remove the in_edges from v - srcs = copy(inneighbors(g, v)) - @inbounds for s in srcs - rem_edge!(g, edgetype(g)(s, v)) - end - # remove the in_edges from the last vertex - neigs = copy(inneighbors(g, n)) - @inbounds for s in neigs - rem_edge!(g, edgetype(g)(s, n)) - end - if v != n - # add the edges from n back to v - @inbounds for s in neigs - if s != n # don't add an edge to the last vertex - see #820. - add_edge!(g, edgetype(g)(s, v)) - else - self_loop_n = true - end - end - end - - if is_directed(g) - # remove the out_edges from v - dsts = copy(outneighbors(g, v)) - @inbounds for d in dsts - rem_edge!(g, edgetype(g)(v, d)) - end - # remove the out_edges from the last vertex - neigs = copy(outneighbors(g, n)) - @inbounds for d in neigs - rem_edge!(g, edgetype(g)(n, d)) - end - if v != n - # add the out_edges back to v - @inbounds for d in neigs - if d != n - add_edge!(g, edgetype(g)(v, d)) - end - end - end - end - if self_loop_n - add_edge!(g, edgetype(g)(v, v)) - end - pop!(g.fadjlist) - if is_directed(g) - pop!(g.badjlist) - end - return true -end - -zero(::Type{G}) where {G<:AbstractSimpleGraph} = G() - -is_range_based(::Type{<:AbstractSimpleGraph}) = true - -include("./simpleedge.jl") -include("./simpledigraph.jl") -include("./simplegraph.jl") -include("./simpleedgeiter.jl") - -end # module diff --git a/src/SimpleGraphs/simpledigraph.jl b/src/SimpleGraphs/simpledigraph.jl index 0a125b2..c30840f 100644 --- a/src/SimpleGraphs/simpledigraph.jl +++ b/src/SimpleGraphs/simpledigraph.jl @@ -1,621 +1,29 @@ -const SimpleDiGraphEdge = SimpleEdge - -""" - SimpleDiGraph{T} - -A type representing a directed graph. -""" -mutable struct SimpleDiGraph{T<:Integer} <: AbstractSimpleGraph{T} - ne::Int - fadjlist::Vector{Vector{T}} # [src]: (dst, dst, dst) - badjlist::Vector{Vector{T}} # [dst]: (src, src, src) - - function SimpleDiGraph{T}( - ne::Int, fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}} - ) where {T} - throw_if_invalid_eltype(T) - return new(ne, fadjlist, badjlist) - end -end - -function SimpleDiGraph( - ne::Int, fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}} -) where {T} - return SimpleDiGraph{T}(ne, fadjlist, badjlist) -end - -# DiGraph{UInt8}(6), DiGraph{Int16}(7), DiGraph{Int8}() -""" - SimpleDiGraph{T}(n=0) - -Construct a `SimpleDiGraph{T}` with `n` vertices and 0 edges. -If not specified, the element type `T` is the type of `n`. - -## Examples -```jldoctest -julia> using Graphs - -julia> SimpleDiGraph(UInt8(10)) -{10, 0} directed simple UInt8 graph -``` -""" -function SimpleDiGraph{T}(n::Integer=0) where {T<:Integer} - fadjlist = [Vector{T}() for _ in one(T):n] - badjlist = [Vector{T}() for _ in one(T):n] - return SimpleDiGraph(0, fadjlist, badjlist) -end - -# SimpleDiGraph(6), SimpleDiGraph(0x5) -SimpleDiGraph(n::T) where {T<:Integer} = SimpleDiGraph{T}(n) - -# SimpleDiGraph() -SimpleDiGraph() = SimpleDiGraph{Int}() - -# SimpleDiGraph(UInt8) -""" - SimpleDiGraph(::Type{T}) - -Construct an empty `SimpleDiGraph{T}` with 0 vertices and 0 edges. - -## Examples -```jldoctest -julia> using Graphs - -julia> SimpleDiGraph(UInt8) -{0, 0} directed simple UInt8 graph -``` -""" -SimpleDiGraph(::Type{T}) where {T<:Integer} = SimpleDiGraph{T}(zero(T)) - -# SimpleDiGraph(adjmx) """ - SimpleDiGraph{T}(adjm::AbstractMatrix) - -Construct a `SimpleDiGraph{T}` from the adjacency matrix `adjm`. -If `adjm[i][j] != 0`, an edge `(i, j)` is inserted. `adjm` must be a square matrix. -The element type `T` can be omitted. - -## Examples -```jldoctest -julia> using Graphs - -julia> A1 = [false true; false false] -2×2 Matrix{Bool}: - 0 1 - 0 0 +$(TYPEDEF) -julia> SimpleDiGraph(A1) -{2, 1} directed simple Int64 graph +A type representing a directed graph with unweighted, non-multiple edges. -julia> A2 = [2 7; 5 0] -2×2 Matrix{Int64}: - 2 7 - 5 0 +# Fields -julia> SimpleDiGraph{Int16}(A2) -{2, 3} directed simple Int16 graph -``` +$(TYPEDFIELDS) """ -SimpleDiGraph(adjmx::AbstractMatrix) = SimpleDiGraph{Int}(adjmx) - -# sparse adjacency matrix constructor: SimpleDiGraph(adjmx) -function SimpleDiGraph{T}(adjmx::SparseMatrixCSC{U}) where {T<:Integer} where {U<:Real} - dima, dimb = size(adjmx) - isequal(dima, dimb) || - throw(ArgumentError("Adjacency / distance matrices must be square")) - - g = SimpleDiGraph(T(dima)) - maxc = length(adjmx.colptr) - @inbounds for c in 1:(maxc - 1) - for rind in 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 SimpleDiGraph{T}(adjmx::AbstractMatrix{U}) where {T<:Integer} where {U<:Real} - dima, dimb = size(adjmx) - isequal(dima, dimb) || - throw(ArgumentError("Adjacency / distance matrices must be square")) - - g = SimpleDiGraph(T(dima)) - @inbounds for i in findall(adjmx .!= zero(U)) - add_edge!(g, i[1], i[2]) - end - return g -end - -# converts DiGraph{Int} to DiGraph{Int32} -""" - SimpleDiGraph{T}(g::SimpleDiGraph) - -Construct a copy of g. -If the element type `T` is specified, the vertices of `g` are converted to this type. -Otherwise the element type is the same as for `g`. - -## Examples -```jldoctest -julia> using Graphs - -julia> g = complete_digraph(5) -{5, 20} directed simple Int64 graph - -julia> SimpleDiGraph{UInt8}(g) -{5, 20} directed simple UInt8 graph -``` -""" -function 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 - -SimpleDiGraph(g::SimpleDiGraph) = copy(g) - -# constructor from abstract graph: SimpleDiGraph(graph) -""" - SimpleDiGraph(g::AbstractSimpleGraph) - -Construct an directed `SimpleDiGraph` from a graph `g`. -The element type is the same as for `g`. - -## Examples -```jldoctest -julia> using Graphs - -julia> g = path_graph(Int8(5)) -{5, 4} undirected simple Int8 graph - -julia> SimpleDiGraph(g) -{5, 8} directed simple Int8 graph -``` -""" -function SimpleDiGraph(g::AbstractSimpleGraph) - h = SimpleDiGraph(nv(g)) - num_self_loops = sum(v -> has_edge(g, v, v), vertices(g); init=0) - h.ne = ne(g) * 2 - num_self_loops - h.fadjlist = deepcopy_adjlist(fadj(g)) - h.badjlist = deepcopy_adjlist(badj(g)) - return h -end - -@inbounds function cleanupedges!( - fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}} -) where {T<:Integer} - neg = 0 - for v in 1:length(fadjlist) - if !issorted(fadjlist[v]) - sort!(fadjlist[v]) - end - if !issorted(badjlist[v]) - sort!(badjlist[v]) - end - unique!(fadjlist[v]) - unique!(badjlist[v]) - neg += length(fadjlist[v]) - end - return neg -end - -""" - SimpleDiGraph(edge_list::Vector) - -Construct a `SimpleDiGraph` from a vector of edges. -The element type is taken from the edges in `edge_list`. -The number of vertices is the highest that is used in an edge in `edge_list`. - -### Implementation Notes -This constructor works the fastest when `edge_list` is sorted -by the lexical ordering and does not contain any duplicates. - -### See also -[`SimpleDiGraphFromIterator`](@ref) - -## Examples -```jldoctest -julia> using Graphs - -julia> el = Edge.([ (1, 3), (1, 5), (3, 1) ]) -3-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: - Edge 1 => 3 - Edge 1 => 5 - Edge 3 => 1 - -julia> SimpleDiGraph(el) -{5, 3} directed simple Int64 graph -``` -""" -function SimpleDiGraph(edge_list::Vector{SimpleDiGraphEdge{T}}) where {T<:Integer} - nvg = zero(T) - @inbounds( - for e in edge_list - 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) - degs_in = zeros(Int, nvg) - @inbounds( - for e in edge_list - s, d = src(e), dst(e) - (s >= 1 && d >= 1) || continue - degs_out[s] += 1 - degs_in[d] += 1 - end - ) - - fadjlist = Vector{Vector{T}}(undef, nvg) - badjlist = Vector{Vector{T}}(undef, nvg) - @inbounds( - for v in 1:nvg - fadjlist[v] = Vector{T}(undef, degs_out[v]) - badjlist[v] = Vector{T}(undef, degs_in[v]) - end - ) - - @inbounds( - for e in edge_list - s, d = src(e), dst(e) - (s >= 1 && d >= 1) || continue - fadjlist[s][list_sizes_out[s]] = d - list_sizes_out[s] += 1 - badjlist[d][list_sizes_in[d]] = s - list_sizes_in[d] += 1 - end - ) - - neg = cleanupedges!(fadjlist, badjlist) - g = SimpleDiGraph{T}() - g.fadjlist = fadjlist - g.badjlist = badjlist - g.ne = neg - - return g -end - -@inbounds function add_to_lists!( - fadjlist::Vector{Vector{T}}, badjlist::Vector{Vector{T}}, s::T, d::T -) where {T<:Integer} - nvg = length(fadjlist) - nvg_new = max(nvg, s, d) - for v in (nvg + 1):nvg_new - push!(fadjlist, Vector{T}()) - push!(badjlist, Vector{T}()) - end - - push!(fadjlist[s], d) - return push!(badjlist[d], s) -end - -# Try to get the eltype from the first element -function _SimpleDiGraphFromIterator(iter)::SimpleDiGraph - next = iterate(iter) - if (next === nothing) - return SimpleDiGraph(0) - end - - e = first(next) - E = typeof(e) - if !(E <: SimpleGraphEdge{<:Integer}) - throw(DomainError(iter, "Edges must be of type SimpleEdge{T <: Integer}")) - end - - T = eltype(e) - g = SimpleDiGraph{T}() - fadjlist = Vector{Vector{T}}() - badjlist = Vector{Vector{T}}() - - while next != nothing - (e, state) = next - - if !(e isa E) - throw(DomainError(iter, "Edges must all have the same type.")) - end - s, d = src(e), dst(e) - if ((s >= 1) & (d >= 1)) - add_to_lists!(fadjlist, badjlist, s, d) - end - - next = iterate(iter, state) - end - - neg = cleanupedges!(fadjlist, badjlist) - g.fadjlist = fadjlist - g.badjlist = badjlist - g.ne = neg - - return g -end - -function _SimpleDiGraphFromIterator(iter, ::Type{T}) where {T<:Integer} - g = SimpleDiGraph{T}() - fadjlist = Vector{Vector{T}}() - badjlist = Vector{Vector{T}}() - - @inbounds( - for e in iter - s, d = src(e), dst(e) - (s >= 1 && d >= 1) || continue - add_to_lists!(fadjlist, badjlist, s, d) - end - ) - - neg = cleanupedges!(fadjlist, badjlist) - g.fadjlist = fadjlist - g.badjlist = badjlist - g.ne = neg - - return g -end - -""" - SimpleDiGraphFromIterator(iter) - -Create a `SimpleDiGraph` from an iterator `iter`. The elements in `iter` must -be of `type <: SimpleEdge`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleDiGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> add_edge!(g, 2, 1); - -julia> h = SimpleDiGraphFromIterator(edges(g)) -{2, 2} directed simple Int64 graph - -julia> collect(edges(h)) -2-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: - Edge 1 => 2 - Edge 2 => 1 -``` -""" -function SimpleDiGraphFromIterator(iter)::SimpleDiGraph - if Base.IteratorEltype(iter) == Base.HasEltype() - E = eltype(iter) - if (E <: SimpleGraphEdge{<:Integer} && isconcretetype(E)) - T = eltype(E) - if isconcretetype(T) - return _SimpleDiGraphFromIterator(iter, T) - end - end - end - - return _SimpleDiGraphFromIterator(iter) -end - -edgetype(::SimpleDiGraph{T}) where {T<:Integer} = SimpleGraphEdge{T} - -badj(g::SimpleDiGraph) = g.badjlist -badj(g::SimpleDiGraph, v::Integer) = badj(g)[v] - -function copy(g::SimpleDiGraph{T}) where {T<:Integer} - return SimpleDiGraph{T}( - g.ne, deepcopy_adjlist(g.fadjlist), deepcopy_adjlist(g.badjlist) - ) -end - -function ==(g::SimpleDiGraph, h::SimpleDiGraph) - return vertices(g) == vertices(h) && - ne(g) == ne(h) && - fadj(g) == fadj(h) && - badj(g) == badj(h) -end - -is_directed(::Type{<:SimpleDiGraph}) = true - -function has_edge(g::SimpleDiGraph{T}, s, d) where {T} - verts = vertices(g) - (s in verts && d in verts) || return false # edge out of bounds - @inbounds list = g.fadjlist[s] - @inbounds list_backedge = g.badjlist[d] - if length(list) > length(list_backedge) - d = s - list = list_backedge - end - return insorted(d, list) -end - -function has_edge(g::SimpleDiGraph{T}, e::SimpleDiGraphEdge{T}) where {T} - s, d = T.(Tuple(e)) - return has_edge(g, s, d) -end - -function add_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] - index = searchsortedfirst(list, d) - @inbounds (index <= length(list) && list[index] == d) && return false # edge already in graph - insert!(list, index, d) - - g.ne += 1 - - @inbounds list = g.badjlist[d] - index = searchsortedfirst(list, s) - insert!(list, index, s) - return true # edge successfully added -end - -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] - 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] - index = searchsortedfirst(list, s) - deleteat!(list, index) - return true # edge successfully removed -end - -function add_vertex!(g::SimpleDiGraph{T}) where {T} - (nv(g) + one(T) <= nv(g)) && return false # test for overflow - push!(g.badjlist, Vector{T}()) - push!(g.fadjlist, Vector{T}()) - - return true +mutable struct SimpleDiGraph{T<:Integer} <: AbstractGraph{T,SimpleEdge{T}} + "Number of edges" + ne::Int + "Forward adjacency list such that `F[v]` contains the out-neighbors of vertex `v`" + fadjlist::Vector{Vector{T}} + "Backward adjacency list such that `B[u]` contains the in-neighbors of vertex `v`" + badjlist::Vector{Vector{T}} end -function rem_vertices!( - g::SimpleDiGraph{T}, vs::AbstractVector{<:Integer}; keep_order::Bool=false -) where {T<:Integer} - # check the implementation in simplegraph.jl for more comments - - n = nv(g) - isempty(vs) && return collect(Base.OneTo(n)) - - # Sort and filter the vertices that we want to remove - remove = sort(vs) - unique!(remove) - (1 <= remove[1] && remove[end] <= n) || - throw(ArgumentError("Vertices to be removed must be in the range 1:nv(g).")) - - # Create a vmap that maps vertices to their new position - # vertices that get removed are mapped to 0 - vmap = Vector{T}(undef, n) - if keep_order - # traverse the vertex list and shift if a vertex gets removed - i = 1 - @inbounds for u in vertices(g) - if i <= length(remove) && u == remove[i] - vmap[u] = 0 - i += 1 - else - vmap[u] = u - (i - 1) - end - end - else - # 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) - v = n - @inbounds for u in vertices(g) - u > v && break - if i <= length(remove) && u == remove[i] - while v == remove[j] && v > u - vmap[v] = 0 - v -= one(T) - j -= 1 - end - # v > remove[j] || u == v - vmap[v] = u - vmap[u] = 0 - v -= one(T) - i += 1 - else - vmap[u] = u - end - end - end - - fadjlist = g.fadjlist - badjlist = g.badjlist - - # count the number of edges that will be removed - num_removed_edges = 0 - @inbounds for u in remove - for v in fadjlist[u] - num_removed_edges += 1 - end - for v in badjlist[u] - if vmap[v] != 0 - num_removed_edges += 1 - end - end - end - g.ne -= num_removed_edges - - # move the lists in the adjacency list to their new position - # order of traversing is important! - @inbounds for u in (keep_order ? (one(T):1:n) : (n:-1:one(T))) - if vmap[u] != 0 - fadjlist[vmap[u]] = fadjlist[u] - badjlist[vmap[u]] = badjlist[u] - end - end - resize!(fadjlist, n - length(remove)) - resize!(badjlist, n - length(remove)) - - # remove vertices from the lists in fadjlist and badjlist - @inbounds for list_of_lists in (fadjlist, badjlist) - for list in list_of_lists - Δ = 0 - for (i, v) in enumerate(list) - if vmap[v] == 0 - Δ += 1 - else - list[i - Δ] = vmap[v] - end - end - resize!(list, length(list) - Δ) - if !keep_order - sort!(list) - end - end - end +GraphsBase.is_directed(::Type{<:SimpleDiGraph}) = true - # we create a reverse vmap, that maps vertices in the result graph - # to the ones in the original graph. This resembles the output of - # induced_subgraph - reverse_vmap = Vector{T}(undef, nv(g)) - @inbounds for (i, u) in enumerate(vmap) - if u != 0 - reverse_vmap[u] = i - end - end +GraphsBase.vertices(g::SimpleDiGraph{T}) where {T} = one(T):T(length(g.fadjlist)) - return reverse_vmap +function GraphsBase.out_edges(g::SimpleDiGraph{T}, u) where {T} + return (SimpleEdge{T}(u, v) for v in g.fadjlist[u]) end -function all_neighbors(g::SimpleDiGraph{T}, u::Integer) where {T} - i, j = 1, 1 - in_nbrs, out_nbrs = inneighbors(g, u), outneighbors(g, u) - in_len, out_len = length(in_nbrs), length(out_nbrs) - union_nbrs = Vector{T}(undef, in_len + out_len) - indx = 1 - @inbounds while i <= in_len && j <= out_len - if in_nbrs[i] < out_nbrs[j] - union_nbrs[indx] = in_nbrs[i] - i += 1 - elseif in_nbrs[i] > out_nbrs[j] - union_nbrs[indx] = out_nbrs[j] - j += 1 - else - union_nbrs[indx] = out_nbrs[j] - i += 1 - j += 1 - end - indx += 1 - end - @inbounds while i <= in_len - union_nbrs[indx] = in_nbrs[i] - i += 1 - indx += 1 - end - @inbounds while j <= out_len - union_nbrs[indx] = out_nbrs[j] - j += 1 - indx += 1 - end - resize!(union_nbrs, indx - 1) - return union_nbrs +function GraphsBase.in_edges(g::SimpleDiGraph{T}, v) where {T} + return (SimpleEdge{T}(u, v) for u in g.badjlist[v]) end diff --git a/src/SimpleGraphs/simpleedge.jl b/src/SimpleGraphs/simpleedge.jl index c07943e..0c6161b 100644 --- a/src/SimpleGraphs/simpleedge.jl +++ b/src/SimpleGraphs/simpleedge.jl @@ -1,37 +1,24 @@ -import Base: Pair, Tuple, show, ==, hash, isless -import GraphsBase: AbstractEdge, src, dst +""" +$(TYPEDEF) -struct SimpleEdge{T<:Integer} <: AbstractSimpleEdge{T} +A type representing an unweighted directed edge. + +# Fields + +$(TYPEDFIELDS) +""" +struct SimpleEdge{T<:Integer} <: AbstractEdge{T,Int} + "Source of the edge" src::T + "Destination of the edge" dst::T end -SimpleEdge(t::Tuple) = SimpleEdge(t[1], t[2]) -SimpleEdge(p::Pair) = SimpleEdge(p.first, p.second) -SimpleEdge{T}(p::Pair) where {T<:Integer} = SimpleEdge(T(p.first), T(p.second)) -SimpleEdge{T}(t::Tuple) where {T<:Integer} = SimpleEdge(T(t[1]), T(t[2])) - -eltype(::Type{<:ET}) where {ET<:AbstractSimpleEdge{T}} where {T} = T - -# 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)") +Base.Tuple(e::SimpleEdge) = (e.src, e.dst) +Base.isless(e1::SimpleEdge, e2::SimpleEdge) = isless(Tuple(e1), Tuple(e2)) +Base.:(==)(e1::SimpleEdge, e2::SimpleEdge) = Tuple(e1) == Tuple(e2) -# Conversions -Pair(e::AbstractSimpleEdge) = Pair(src(e), dst(e)) -Tuple(e::AbstractSimpleEdge) = (src(e), dst(e)) - -SimpleEdge{T}(e::AbstractSimpleEdge) where {T<:Integer} = SimpleEdge{T}(T(e.src), T(e.dst)) - -# Convenience functions -reverse(e::T) where {T<:AbstractSimpleEdge} = T(dst(e), src(e)) -function ==(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) - return (src(e1) == src(e2) && dst(e1) == dst(e2)) -end -hash(e::AbstractSimpleEdge, h::UInt) = hash(src(e), hash(dst(e), h)) -function isless(e1::AbstractSimpleEdge, e2::AbstractSimpleEdge) - return (src(e1) < src(e2)) || ((src(e1) == src(e2)) && (dst(e1) < dst(e2))) -end +GraphsBase.src(e::SimpleEdge) = e.src +GraphsBase.dst(e::SimpleEdge) = e.dst +GraphsBase.weight(e::SimpleEdge) = 1 +Base.reverse(e::SimpleEdge) = SimpleEdge(e.dst, e.src) diff --git a/src/SimpleGraphs/simpleedgeiter.jl b/src/SimpleGraphs/simpleedgeiter.jl deleted file mode 100644 index 079981d..0000000 --- a/src/SimpleGraphs/simpleedgeiter.jl +++ /dev/null @@ -1,123 +0,0 @@ -""" - SimpleEdgeIter - -The function [`edges`](@ref) returns a `SimpleEdgeIter` for `AbstractSimpleGraph`s. -The iterates are in lexicographical order, smallest first. The iterator is valid for -one pass over the edges, and is invalidated by changes to the graph. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = path_graph(3); - -julia> es = edges(g) -SimpleEdgeIter 2 - -julia> e_it = iterate(es) -(Edge 1 => 2, (1, 2)) - -julia> iterate(es, e_it[2]) -(Edge 2 => 3, (2, 3)) -``` -""" -struct SimpleEdgeIter{G} <: AbstractEdgeIter - g::G -end - -eltype(::Type{SimpleEdgeIter{SimpleGraph{T}}}) where {T} = SimpleGraphEdge{T} -eltype(::Type{SimpleEdgeIter{SimpleDiGraph{T}}}) where {T} = SimpleDiGraphEdge{T} - -@traitfn @inline function iterate( - eit::SimpleEdgeIter{G}, state=(one(eltype(eit.g)), 1) -) where {G <: AbstractSimpleGraph; !IsDirected{G}} - g = eit.g - fadjlist = fadj(g) - T = eltype(g) - n = T(nv(g)) - u, i = state - - @inbounds while u < n - list_u = fadjlist[u] - if i > length(list_u) - u += one(u) - i = searchsortedfirst(fadjlist[u], u) - continue - end - e = SimpleEdge(u, list_u[i]) - state = (u, i + 1) - return e, state - end - - @inbounds (n == 0 || i > length(fadjlist[n])) && return nothing - - e = SimpleEdge(n, n) - state = (u, i + 1) - return e, state -end - -@traitfn @inline function iterate( - eit::SimpleEdgeIter{G}, state=(one(eltype(eit.g)), 1) -) where {G <: AbstractSimpleGraph; IsDirected{G}} - g = eit.g - fadjlist = fadj(g) - T = eltype(g) - n = T(nv(g)) - u, i = state - - n == 0 && return nothing - - @inbounds while true - list_u = fadjlist[u] - if i > length(list_u) - u == n && return nothing - - u += one(u) - list_u = fadjlist[u] - i = 1 - continue - end - e = SimpleEdge(u, list_u[i]) - state = (u, i + 1) - return e, state - end - - return nothing -end - -length(eit::SimpleEdgeIter) = ne(eit.g) - -function _isequal(e1::SimpleEdgeIter, e2) - k = 0 - for e in e2 - has_edge(e1.g, e) || return false - k += 1 - end - return k == ne(e1.g) -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) - g = e1.g - h = e2.g - ne(g) == ne(h) || return false - m = min(nv(g), nv(h)) - for i in 1:m - fadj(g, i) == fadj(h, i) || return false - end - nv(g) == nv(h) && return true - for i in (m + 1):nv(g) - isempty(fadj(g, i)) || return false - end - for i in (m + 1):nv(h) - isempty(fadj(h, i)) || return false - end - return true -end - -in(e, es::SimpleEdgeIter) = has_edge(es.g, e) - -show(io::IO, eit::SimpleEdgeIter) = write(io, "SimpleEdgeIter $(ne(eit.g))") diff --git a/src/SimpleGraphs/simplegraph.jl b/src/SimpleGraphs/simplegraph.jl index 359c399..21f6310 100644 --- a/src/SimpleGraphs/simplegraph.jl +++ b/src/SimpleGraphs/simplegraph.jl @@ -1,684 +1,25 @@ -const SimpleGraphEdge = SimpleEdge - -""" - SimpleGraph{T} - -A type representing an undirected graph. -""" -mutable struct SimpleGraph{T<:Integer} <: AbstractSimpleGraph{T} - ne::Int - fadjlist::Vector{Vector{T}} # [src]: (dst, dst, dst) - - function SimpleGraph{T}(ne::Int, fadjlist::Vector{Vector{T}}) where {T} - throw_if_invalid_eltype(T) - return new{T}(ne, fadjlist) - end -end - -function SimpleGraph(ne, fadjlist::Vector{Vector{T}}) where {T} - return SimpleGraph{T}(ne, fadjlist) -end - -# Graph{UInt8}(6), Graph{Int16}(7), Graph{UInt8}() -""" - SimpleGraph{T}(n=0) - -Construct a `SimpleGraph{T}` with `n` vertices and 0 edges. -If not specified, the element type `T` is the type of `n`. - -## Examples -```jldoctest -julia> using Graphs - -julia> SimpleGraph(UInt8(10)) -{10, 0} undirected simple UInt8 graph -``` -""" -function SimpleGraph{T}(n::Integer=0) where {T<:Integer} - fadjlist = [Vector{T}() for _ in one(T):n] - return SimpleGraph{T}(0, fadjlist) -end - -# SimpleGraph(6), SimpleGraph(0x5) -SimpleGraph(n::T) where {T<:Integer} = SimpleGraph{T}(n) - -# SimpleGraph() -SimpleGraph() = SimpleGraph{Int}() - -# SimpleGraph(UInt8) -""" - SimpleGraph(::Type{T}) - -Construct an empty `SimpleGraph{T}` with 0 vertices and 0 edges. - -## Examples -```jldoctest -julia> using Graphs - -julia> SimpleGraph(UInt8) -{0, 0} undirected simple UInt8 graph -``` -""" -SimpleGraph(::Type{T}) where {T<:Integer} = SimpleGraph{T}(zero(T)) - -# SimpleGraph(adjmx) -""" - SimpleGraph{T}(adjm::AbstractMatrix) - -Construct a `SimpleGraph{T}` from the adjacency matrix `adjm`. -If `adjm[i][j] != 0`, an edge `(i, j)` is inserted. `adjm` must be a square and symmetric matrix. -The element type `T` can be omitted. - -## Examples -```jldoctest -julia> using Graphs - -julia> A1 = [false true; true false]; - -julia> SimpleGraph(A1) -{2, 1} undirected simple Int64 graph - -julia> A2 = [2 7; 7 0]; - -julia> SimpleGraph{Int16}(A2) -{2, 2} undirected simple Int16 graph -``` """ -SimpleGraph(adjmx::AbstractMatrix) = SimpleGraph{Int}(adjmx) +$(TYPEDEF) -# Graph{UInt8}(adjmx) -function SimpleGraph{T}(adjmx::AbstractMatrix) where {T<:Integer} - dima, dimb = size(adjmx) - isequal(dima, dimb) || - throw(ArgumentError("Adjacency / distance matrices must be square")) - issymmetric(adjmx) || - throw(ArgumentError("Adjacency / distance matrices must be symmetric")) +A type representing an undirected graph with unweighted, non-multiple edges. - g = SimpleGraph(T(dima)) - @inbounds for i in findall(triu(adjmx) .!= 0) - add_edge!(g, i[1], i[2]) - end - return g -end - -# SimpleGraph of a SimpleGraph -""" - SimpleGraph{T}(g::SimpleGraph) - -Construct a copy of g. -If the element type `T` is specified, the vertices of `g` are converted to this type. -Otherwise the element type is the same as for `g`. - -## Examples -```jldoctest -julia> using Graphs - -julia> g = complete_graph(5) -{5, 10} undirected simple Int64 graph - -julia> SimpleGraph{UInt8}(g) -{5, 10} undirected simple UInt8 graph -``` -""" -SimpleGraph(g::SimpleGraph) = copy(g) - -# converts Graph{Int} to Graph{Int32} -function SimpleGraph{T}(g::SimpleGraph) where {T<:Integer} - h_fadj = [Vector{T}(x) for x in fadj(g)] - return SimpleGraph(ne(g), h_fadj) -end +# Fields -# SimpleGraph(digraph) +$(TYPEDFIELDS) """ - SimpleGraph(g::SimpleDiGraph) - -Construct an undirected `SimpleGraph` from a directed `SimpleDiGraph`. -Every directed edge in `g` is added as an undirected edge. -The element type is the same as for `g`. - -## Examples -```jldoctest -julia> using Graphs - -julia> g = path_digraph(Int8(5)) -{5, 4} directed simple Int8 graph - -julia> SimpleGraph(g) -{5, 4} undirected simple Int8 graph -``` -""" -function SimpleGraph(g::SimpleDiGraph) - gnv = nv(g) - edgect = 0 - newfadj = deepcopy_adjlist(g.fadjlist) - @inbounds for i in vertices(g) - for j in badj(g, i) - index = searchsortedfirst(newfadj[i], j) - if index <= length(newfadj[i]) && newfadj[i][index] == j - edgect += 1 # this is an existing edge - we already have it - if i == j - edgect += 1 # need to count self loops - end - else - insert!(newfadj[i], index, j) - edgect += 2 # this is a new edge only in badjlist - end - end - end - iseven(edgect) || - throw(AssertionError("invalid edgect in graph creation - please file bug report")) - return SimpleGraph(edgect ÷ 2, newfadj) -end - -@inbounds function cleanupedges!(fadjlist::Vector{Vector{T}}) where {T<:Integer} - neg = 0 - for v in 1:length(fadjlist) - if !issorted(fadjlist[v]) - sort!(fadjlist[v]) - end - unique!(fadjlist[v]) - neg += length(fadjlist[v]) - # self-loops should count as one edge - for w in fadjlist[v] - if w == v - neg += 1 - break - end - end - end - return neg ÷ 2 -end - -""" - SimpleGraph(edge_list::Vector) - -Construct a `SimpleGraph` from a vector of edges. -The element type is taken from the edges in `edge_list`. -The number of vertices is the highest that is used in an edge in `edge_list`. - -### Implementation Notes -This constructor works the fastest when `edge_list` is sorted -by the lexical ordering and does not contain any duplicates. - -### See also -[`SimpleGraphFromIterator`](@ref) - -## Examples -```jldoctest -julia> using Graphs - -julia> el = Edge.([ (1, 2), (1, 5) ]) -2-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: - Edge 1 => 2 - Edge 1 => 5 - -julia> SimpleGraph(el) -{5, 2} undirected simple Int64 graph -``` -""" -function SimpleGraph(edge_list::Vector{SimpleGraphEdge{T}}) where {T<:Integer} - nvg = zero(T) - @inbounds( - for e in edge_list - nvg = max(nvg, src(e), dst(e)) - end - ) - - list_sizes = ones(Int, nvg) - degs = zeros(Int, nvg) - @inbounds( - for e in edge_list - s, d = src(e), dst(e) - (s >= 1 && d >= 1) || continue - degs[s] += 1 - if s != d - degs[d] += 1 - end - end - ) - - fadjlist = Vector{Vector{T}}(undef, nvg) - @inbounds( - for v in 1:nvg - fadjlist[v] = Vector{T}(undef, degs[v]) - end - ) - - @inbounds( - for e in edge_list - s, d = src(e), dst(e) - (s >= 1 && d >= 1) || continue - fadjlist[s][list_sizes[s]] = d - list_sizes[s] += 1 - if s != d - fadjlist[d][list_sizes[d]] = s - list_sizes[d] += 1 - end - end - ) - - neg = cleanupedges!(fadjlist) - g = SimpleGraph{T}() - g.fadjlist = fadjlist - g.ne = neg - - return g -end - -@inbounds function add_to_fadjlist!( - fadjlist::Vector{Vector{T}}, s::T, d::T -) where {T<:Integer} - nvg = length(fadjlist) - nvg_new = max(nvg, s, d) - for v in (nvg + 1):nvg_new - push!(fadjlist, Vector{T}()) - end - - push!(fadjlist[s], d) - if s != d - push!(fadjlist[d], s) - end -end - -# Try to get the eltype from the first element -function _SimpleGraphFromIterator(iter)::SimpleGraph - next = iterate(iter) - if (next === nothing) - return SimpleGraph(0) - end - - e = first(next) - E = typeof(e) - if !(E <: SimpleGraphEdge{<:Integer}) - throw(DomainError(iter, "Edges must be of type SimpleEdge{T <: Integer}")) - end - - T = eltype(e) - g = SimpleGraph{T}() - fadjlist = Vector{Vector{T}}() - - while next != nothing - (e, state) = next - - if !(e isa E) - throw(DomainError(iter, "Edges must all have the same type.")) - end - s, d = src(e), dst(e) - if ((s >= 1) & (d >= 1)) - add_to_fadjlist!(fadjlist, s, d) - end - - next = iterate(iter, state) - end - - neg = cleanupedges!(fadjlist) - g.fadjlist = fadjlist - g.ne = neg - - return g -end - -function _SimpleGraphFromIterator(iter, ::Type{T}) where {T<:Integer} - g = SimpleGraph{T}() - fadjlist = Vector{Vector{T}}() - - @inbounds( - for e in iter - s, d = src(e), dst(e) - (s >= 1 && d >= 1) || continue - add_to_fadjlist!(fadjlist, s, d) - end - ) - - neg = cleanupedges!(fadjlist) - g.fadjlist = fadjlist - g.ne = neg - - return g -end - -""" - SimpleGraphFromIterator(iter) - -Create a [`SimpleGraph`](@ref) from an iterator `iter`. The elements in iter must -be of `type <: SimpleEdge`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(3); - -julia> add_edge!(g, 1, 2); - -julia> add_edge!(g, 2, 3); - -julia> h = SimpleGraphFromIterator(edges(g)); - -julia> collect(edges(h)) -2-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}: - Edge 1 => 2 - Edge 2 => 3 -``` -""" -function SimpleGraphFromIterator(iter)::SimpleGraph - if Base.IteratorEltype(iter) == Base.HasEltype() - E = eltype(iter) - if (E <: SimpleGraphEdge{<:Integer} && isconcretetype(E)) - T = eltype(E) - if isconcretetype(T) - return _SimpleGraphFromIterator(iter, T) - end - end - end - - return _SimpleGraphFromIterator(iter) -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 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. -""" -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 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. -""" -adj(g::SimpleGraph) = fadj(g) -adj(g::SimpleGraph, v::Integer) = fadj(g, v) - -copy(g::SimpleGraph) = SimpleGraph(g.ne, deepcopy_adjlist(g.fadjlist)) - -function ==(g::SimpleGraph, h::SimpleGraph) - return vertices(g) == vertices(h) && ne(g) == ne(h) && fadj(g) == fadj(h) -end - -""" - is_directed(g) - -Return `true` if `g` is a directed graph. -""" -is_directed(::Type{<:SimpleGraph}) = false - -function has_edge(g::SimpleGraph{T}, s, d) where {T} - verts = vertices(g) - (s in verts && d in verts) || return false # edge out of bounds - @inbounds list_s = g.fadjlist[s] - @inbounds list_d = g.fadjlist[d] - if length(list_s) > length(list_d) - d = s - list_s = list_d - end - return insorted(d, list_s) -end - -function has_edge(g::SimpleGraph{T}, e::SimpleGraphEdge{T}) where {T} - s, d = T.(Tuple(e)) - return has_edge(g, s, d) -end - -""" - add_edge!(g, e) - -Add an edge `e` to graph `g`. Return `true` if edge was added successfully, -otherwise return `false`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(2); - -julia> add_edge!(g, 1, 2) -true - -julia> add_edge!(g, 2, 3) -false -``` -""" -function add_edge!(g::SimpleGraph{T}, e::SimpleGraphEdge{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] - index = searchsortedfirst(list, d) - @inbounds (index <= length(list) && list[index] == d) && return false # edge already in graph - insert!(list, index, d) - - g.ne += 1 - s == d && return true # selfloop - - @inbounds list = g.fadjlist[d] - index = searchsortedfirst(list, s) - insert!(list, index, s) - return true # edge successfully added -end - -""" - rem_edge!(g, e) - -Remove an edge `e` from graph `g`. Return `true` if edge was removed successfully, -otherwise return `false`. - -### Implementation Notes -If `rem_edge!` returns `false`, the graph may be in an indeterminate state, as -there are multiple points where the function can exit with `false`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> rem_edge!(g, 1, 2) -true - -julia> rem_edge!(g, 1, 2) -false -``` -""" -function rem_edge!(g::SimpleGraph{T}, e::SimpleGraphEdge{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] - index = searchsortedfirst(list, d) - @inbounds (index <= length(list) && list[index] == d) || return false # edge not in graph - deleteat!(list, index) - - g.ne -= 1 - s == d && return true # selfloop - - @inbounds list = g.fadjlist[d] - index = searchsortedfirst(list, s) - deleteat!(list, index) - return true # edge successfully removed +mutable struct SimpleGraph{T<:Integer} <: AbstractGraph{T,SimpleEdge{T}} + """Number of edges""" + ne::Int + "Adjacency list such that `L[v]` contains the neighbors of vertex `v`" + adjlist::Vector{Vector{T}} end -fd -""" - add_vertex!(g) - -Add a new vertex to the graph `g`. Return `true` if addition was successful. - -# Examples -```jldoctest -julia> using Graphs +GraphsBase.is_directed(::Type{<:SimpleGraph}) = false -julia> g = SimpleGraph(Int8(typemax(Int8) - 1)) -{126, 0} undirected simple Int8 graph +GraphsBase.vertices(g::SimpleGraph{T}) where {T} = one(T):T(length(g.adjlist)) -julia> add_vertex!(g) -true - -julia> add_vertex!(g) -false -``` -""" -function add_vertex!(g::SimpleGraph{T}) where {T} - (nv(g) + one(T) <= nv(g)) && return false # test for overflow - push!(g.fadjlist, Vector{T}()) - return true +function GraphsBase.out_edges(g::SimpleGraph{T}, u) where {T} + return (SimpleEdge{T}(u, v) for v in g.adjlist[u]) end -""" - rem_vertices!(g, vs, keep_order=false) -> vmap - -Remove all vertices in `vs` from `g`. -Return a vector `vmap` that maps the vertices in the modified graph to the ones in -the unmodified graph. -If `keep_order` is `true`, the vertices in the modified graph appear in the same -order as they did in the unmodified graph. This might be slower. - -### Implementation Notes -This function is not part of the official Graphs API and is subject to change/removal between major versions. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = complete_graph(5) -{5, 10} undirected simple Int64 graph - -julia> vmap = rem_vertices!(g, [2, 4], keep_order=true); - -julia> vmap -3-element Vector{Int64}: - 1 - 3 - 5 - -julia> g -{3, 3} undirected simple Int64 graph -``` -""" -function rem_vertices!( - g::SimpleGraph{T}, vs::AbstractVector{<:Integer}; keep_order::Bool=false -) where {T<:Integer} - # TODO There might be some room for performance improvements. - # At the moment, we check for all edges if they stay in the graph. - # If some vertices keep their position, this might be unnecessary. - - n = nv(g) - isempty(vs) && return collect(Base.OneTo(n)) - - # Sort and filter the vertices that we want to remove - remove = sort(vs) - unique!(remove) - (1 <= remove[1] && remove[end] <= n) || - throw(ArgumentError("Vertices to be removed must be in the range 1:nv(g).")) - - # Create a vmap that maps vertices to their new position - # vertices that get removed are mapped to 0 - vmap = Vector{T}(undef, n) - - if keep_order - # traverse the vertex list and shift if a vertex gets removed - i = 1 - @inbounds for u in vertices(g) - if i <= length(remove) && u == remove[i] - vmap[u] = 0 - i += 1 - else - vmap[u] = u - (i - 1) - end - end - else - # 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) - v = n - @inbounds for u in vertices(g) - u > v && break - if i <= length(remove) && u == remove[i] - while v == remove[j] && v > u - vmap[v] = 0 - v -= one(T) - j -= 1 - end - # v > remove[j] || u == v - vmap[v] = u - vmap[u] = 0 - v -= one(T) - i += 1 - else - vmap[u] = u - end - end - end - - fadjlist = g.fadjlist - - # count the number of edges that will be removed - # for an edge that gets removed we have to ensure that - # such an edge does not get counted twice when both endpoints - # get removed. That's why we relay on the ordering >= on the vertices. - num_removed_edges = 0 - @inbounds for u in remove - for v in fadjlist[u] - if v >= u || vmap[v] != 0 - num_removed_edges += 1 - end - end - end - g.ne -= num_removed_edges - - # move the lists in the adjacency list to their new position - # The order of traversal is very important here, as otherwise we - # could overwrite lists, that we want to keep! - @inbounds for u in (keep_order ? (one(T):1:n) : (n:-1:one(T))) - if vmap[u] != 0 - fadjlist[vmap[u]] = fadjlist[u] - end - end - resize!(fadjlist, n - length(remove)) - - # remove vertices from the lists in fadjlist - @inbounds for list in fadjlist - Δ = 0 - for (i, v) in enumerate(list) - if vmap[v] == 0 - Δ += 1 - else - list[i - Δ] = vmap[v] - end - end - resize!(list, length(list) - Δ) - if !keep_order - sort!(list) - end - end - - # we create a reverse vmap, that maps vertices in the result graph - # to the ones in the original graph. This resembles the output of - # induced_subgraph - reverse_vmap = Vector{T}(undef, nv(g)) - @inbounds for (i, u) in enumerate(vmap) - if u != 0 - reverse_vmap[u] = i - end - end - - return reverse_vmap -end +GraphsBase.in_edges(g::SimpleGraph, v) = out_edges(g, v) diff --git a/src/SimpleWeightedGraphs/SimpleWeightedGraphs.jl b/src/SimpleWeightedGraphs/SimpleWeightedGraphs.jl new file mode 100644 index 0000000..91729ca --- /dev/null +++ b/src/SimpleWeightedGraphs/SimpleWeightedGraphs.jl @@ -0,0 +1,15 @@ +module SimpleWeightedGraphs + +using DocStringExtensions +using SparseArrays +using GraphsBase + +export SimpleWeightedEdge +export SimpleWeightedGraph +export SimpleWeightedDiGraph + +include("simpleweightededge.jl") +include("simpleweightedgraph.jl") +include("simpleweighteddigraph.jl") + +end diff --git a/src/SimpleWeightedGraphs/simpleweighteddigraph.jl b/src/SimpleWeightedGraphs/simpleweighteddigraph.jl new file mode 100644 index 0000000..c68542d --- /dev/null +++ b/src/SimpleWeightedGraphs/simpleweighteddigraph.jl @@ -0,0 +1,27 @@ +""" +$(TYPEDEF) + +A type representing a directed graph with weighted, non-multiple edges. + +# Fields + +$(TYPEDFIELDS) +""" +struct SimpleWeightedDiGraph{T<:Integer,W} <: AbstractGraph{T,SimpleWeightedEdge{T,W}} + "Transposed weighted adjacency matrix: `weights[v, u]` contains the weight of edge `(u, v)`" + weights::SparseMatrixCSC{W,T} +end + +GraphsBase.is_directed(::Type{<:SimpleWeightedDiGraph}) = true +GraphsBase.vertices(g::SimpleWeightedDiGraph{T}) where {T} = one(T):T(size(g.weights, 1)) + +function GraphsBase.out_edges(g::SimpleWeightedDiGraph{T}, u) where {T} + A = g.weights + return (SimpleWeightedEdge{T}(u, A.rowval[i], A.nzval[i]) for i in nzrange(A, u)) +end + +function GraphsBase.in_edges(g::SimpleWeightedDiGraph, v) + A = g.weights + a = A[v, :] + return (SimpleWeightedEdge{T}(u, v, a[u]) for u in a.nzind) +end diff --git a/src/SimpleWeightedGraphs/simpleweightededge.jl b/src/SimpleWeightedGraphs/simpleweightededge.jl new file mode 100644 index 0000000..b27bd43 --- /dev/null +++ b/src/SimpleWeightedGraphs/simpleweightededge.jl @@ -0,0 +1,26 @@ +""" +$(TYPEDEF) + +A type representing a weighted directed edge. + +# Fields + +$(TYPEDFIELDS) +""" +struct SimpleWeightedEdge{T<:Integer,W} <: AbstractEdge{T,W} + "Source of the edge" + src::T + "Destination of the edge" + dst::T + "Weight of the edge" + weight::W +end + +Base.Tuple(e::SimpleWeightedEdge) = (e.src, e.dst, e.weight) +Base.isless(e1::SimpleWeightedEdge, e2::SimpleWeightedEdge) = isless(Tuple(e1), Tuple(e2)) +Base.:(==)(e1::SimpleWeightedEdge, e2::SimpleWeightedEdge) = Tuple(e1) == Tuple(e2) + +GraphsBase.src(e::SimpleWeightedEdge) = e.src +GraphsBase.dst(e::SimpleWeightedEdge) = e.dst +GraphsBase.weight(e::SimpleWeightedEdge) = e.weight +Base.reverse(e::SimpleWeightedEdge) = SimpleEdge(e.dst, e.src, e.weight) diff --git a/src/SimpleWeightedGraphs/simpleweightedgraph.jl b/src/SimpleWeightedGraphs/simpleweightedgraph.jl new file mode 100644 index 0000000..f5bc16f --- /dev/null +++ b/src/SimpleWeightedGraphs/simpleweightedgraph.jl @@ -0,0 +1,23 @@ +""" +$(TYPEDEF) + +A type representing an undirected graph with weighted, non-multiple edges. + +# Fields + +$(TYPEDFIELDS) +""" +struct SimpleWeightedGraph{T<:Integer,W} <: AbstractGraph{T,SimpleWeightedEdge{T,W}} + "Symmetric weighted adjacency matrix: `weights[u, v]` and `weights[v, u]` both contain the weight of edge `{u, v}`" + weights::SparseMatrixCSC{W,T} +end + +GraphsBase.is_directed(::Type{<:SimpleWeightedGraph}) = false +GraphsBase.vertices(g::SimpleWeightedGraph{T}) where {T} = one(T):T(size(g.weights, 1)) + +function GraphsBase.out_edges(g::SimpleWeightedGraph{T}, u) where {T} + A = g.weights + return (SimpleWeightedEdge{T}(u, A.rowval[i], A.nzval[i]) for i in nzrange(A, u)) +end + +GraphsBase.in_edges(g::SimpleWeightedGraph, v) = out_edges(g, v) diff --git a/src/core.jl b/src/core.jl deleted file mode 100644 index 778daa0..0000000 --- a/src/core.jl +++ /dev/null @@ -1,243 +0,0 @@ -""" - AbstractPathState - -An abstract type that provides information from shortest paths calculations. -""" -abstract type AbstractPathState end - -""" - is_ordered(e) - -Return true if the source vertex of edge `e` is less than or equal to -the destination vertex. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = DiGraph(2); - -julia> add_edge!(g, 2, 1); - -julia> is_ordered(first(edges(g))) -false -``` -""" -is_ordered(e::AbstractEdge) = src(e) <= dst(e) - -""" - 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) - -""" - indegree(g[, v]) - -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`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = DiGraph(3); - -julia> add_edge!(g, 2, 3); - -julia> add_edge!(g, 3, 1); - -julia> indegree(g) -3-element Array{Int64,1}: - 1 - 0 - 1 -``` -""" -indegree(g::AbstractGraph, v::Integer) = length(inneighbors(g, v)) -indegree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [indegree(g, x) for x in v] - -""" - outdegree(g[, v]) - -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`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = DiGraph(3); - -julia> add_edge!(g, 2, 3); - -julia> add_edge!(g, 3, 1); - -julia> outdegree(g) -3-element Array{Int64,1}: - 0 - 1 - 1 -``` -""" -outdegree(g::AbstractGraph, v::Integer) = length(outneighbors(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. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = DiGraph(3); - -julia> add_edge!(g, 2, 3); - -julia> add_edge!(g, 3, 1); - -julia> degree(g) -3-element Array{Int64,1}: - 1 - 1 - 2 -``` -""" -function degree end -@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] - -""" - has_self_loops(g) - -Return true if `g` has any self loops. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> has_self_loops(g) -false - -julia> add_edge!(g, 1, 1); - -julia> has_self_loops(g) -true -``` -""" -has_self_loops(g::AbstractGraph) = - nv(g) == 0 ? false : any(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 in 1:n]) - -""" - weights(g) - -Return the weights of the edges of a graph `g` as a matrix. Defaults -to [`Graphs.DefaultDistance`](@ref). - -### Implementation Notes -In general, referencing the weight of a nonexistent edge is undefined behavior. Do not rely on the `weights` matrix -as a substitute for the graph's [`adjacency_matrix`](@ref). -""" -weights(g::AbstractGraph) = DefaultDistance(nv(g)) diff --git a/src/interface.jl b/src/interface.jl deleted file mode 100644 index 11275e6..0000000 --- a/src/interface.jl +++ /dev/null @@ -1,567 +0,0 @@ -# This file contains the common interface for Graphs. - -""" - NotImplementedError{M}(m) - -`Exception` thrown when a method from the `AbstractGraph` interface -is not implemented by a given graph type. -""" -struct NotImplementedError{M} <: Exception - m::M - NotImplementedError(m::M) where {M} = new{M}(m) -end - -function Base.showerror(io::IO, ie::NotImplementedError) - return print(io, "method $(ie.m) not implemented.") -end - -_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{V,U} end -# abstract type AbstractWeightedEdge{V, U} <: AbstractEdge{V} end - -""" - AbstractEdgeIter - -An abstract type representing an edge iterator. -""" -abstract type AbstractEdgeIter end - -""" - AbstractGraph - -An abstract type representing a multi-graph. -- `V` : Vertex type -- `E` : Edge type - -""" -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) - -# TODO: We can't define isless because it is type piracy. -# We should probably document that it needs isless and == to be implemented -# -# Interface for AbstractVertex -# -# import Base.isless -# """ -# 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 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) - -Return the source vertex of edge `e`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> src(first(edges(g))) -1 -``` -""" -src(e::AbstractEdge) = _NI("src") - -""" - dst(e) - -Return the destination vertex of edge `e`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> dst(first(edges(g))) -2 -``` -""" -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") - -""" - reverse(e) - -Create a new edge from `e` with source and destination vertices reversed. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleDiGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> reverse(first(edges(g))) -Edge 2 => 1 -``` -""" -reverse(e::AbstractEdge) = _NI("reverse") - -# -# Interface for AbstractGraphs -# -""" - edgetype(g) - -Return the type of graph `g`'s edge -""" -edgetype(g::AbstractGraph{V,E}) where {V,E} = E - -""" - eltype(g) - -Return the type of the graph's vertices -""" -eltype(g::AbstractGraph{V,E}) where {V,E} = V - -""" - 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`. - -# Examples -```jldoctest -julia> using Graphs - -julia> collect(vertices(SimpleGraph(4))) -4-element Array{Int64,1}: - 1 - 2 - 3 - 4 -``` -""" -vertices(g::AbstractGraph) = _NI("vertices") - -""" - get_edges(g, u, v) - -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 -julia> using Graphs - -julia> g = path_graph(3); - -julia> collect(get_edges(g, 1, 2)) -1-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: - Edge 1 => 2 -``` -""" -@traitfn get_edges(g::AbstractGraph, u::V, v::V) where {V; AbstractVertex{V}} = - _NI("get_edges") - -""" - edges(g) - -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 -is invalidated by changes to `g`. - -# Examples -```jldoctest -julia> using Graphs - -julia> g = path_graph(3); - -julia> collect(edges(g)) -2-element Array{Graphs.SimpleGraphs.SimpleEdge{Int64},1}: - Edge 1 => 2 - Edge 2 => 3 -``` -""" -edges(g::AbstractGraph) = _NI("edges") - -""" - outedges(g, u) - -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 -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 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) - -Return `true` if the graph type `G` is a directed graph; `false` otherwise. -New graph types must implement `is_directed(::Type{<:G})`. -The method can also be called with `is_directed(g::G)` -# Examples -```jldoctest -julia> using Graphs - -julia> is_directed(SimpleGraph(2)) -false - -julia> is_directed(SimpleGraph) -false - -julia> is_directed(SimpleDiGraph(2)) -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) - -Return true if `v` is a vertex of `g`. - -# Examples -```jldoctest -julia> using Graphs - -julia> has_vertex(SimpleGraph(2), 1) -true - -julia> has_vertex(SimpleGraph(2), 3) -false -``` -""" -has_vertex(g, v) = _NI("has_vertex") - -""" - has_edge(g, s, d) - -Return true if the graph `g` has an edge from node `s` to node `d`. - -An optional `has_edge(g, e)` can be implemented to check if an edge belongs -to a graph, including any data other than source and destination node. - -`e ∈ edges(g)` or `e ∈ edges(g)` evaluate as -calls to `has_edge`, c.f. [`edges`](@ref). - -# Examples -```jldoctest -julia> using Graphs - -julia> g = SimpleDiGraph(2); - -julia> add_edge!(g, 1, 2); - -julia> has_edge(g, 1, 2) -true - -julia> has_edge(g, 2, 1) -false -``` -""" -has_edge(g, s, d) = _NI("has_edge") -has_edge(g, e) = has_edge(g, src(e), dst(e)) - -""" - inneighbors(g, v) - -Return a list of all neighbors connected to vertex `v` by an incoming edge. - -### 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> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); - -julia> inneighbors(g, 4) -2-element Array{Int64,1}: - 3 - 5 -``` -""" -inneighbors(g, v) = _NI("inneighbors") - -""" - outneighbors(g, v) - -Return a list of all neighbors connected to vertex `v` by an outgoing edge. - -# 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> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); - -julia> outneighbors(g, 4) -1-element Array{Int64,1}: - 5 -``` -""" -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) - -Return a zero-vertex, zero-edge version of the graph type `G`. -The fallback is defined for graph values `zero(g::G) = zero(G)`. - -# Examples -```jldoctest -julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); - -julia> zero(typeof(g)) -{0, 0} directed simple Int64 graph - -julia> zero(g) -{0, 0} directed simple Int64 graph -``` -""" -zero(::Type{<:AbstractGraph}) = _NI("zero") - -zero(g::G) where {G<:AbstractGraph} = zero(G) diff --git a/src/interface/abstractedge.jl b/src/interface/abstractedge.jl new file mode 100644 index 0000000..a884d99 --- /dev/null +++ b/src/interface/abstractedge.jl @@ -0,0 +1,98 @@ +""" + AbstractEdge{V,W} + +Abstract type for graph edges with vertices of type `V` and a weight of type `W`. + +A subtype `E` satisfies this interface if both `V` and `E` can be hashed and compared, and if the following methods are implemented: + +- `src(e::E)` +- `dst(e::E)` +- `weight(e::E)` +- `Base.reverse(e::E)` +""" +abstract type AbstractEdge{V,W} end + +## Element types + +""" + eltype(e) + eltype(E) + +Return the type of the vertices of an edge `e` / an edge type `E`. +""" +Base.eltype(::Type{<:AbstractEdge{V}}) where {V} = V +Base.eltype(::E) where {E<:AbstractEdge} = eltype(E) + +""" + weighttype(e) + weighttype(E) + +Return the type of the weights of an edge `e` / an edge type `E`. +""" +weighttype(::Type{<:AbstractEdge{V,W}}) where {V,W} = W +weighttype(::E) where {E<:AbstractEdge} = weighttype(E) + +## Interface functions + +""" + src(e) + +Return the source vertex of edge `e`. +""" +function src end + +""" + dst(e) + +Return the destination vertex of edge `e`. +""" +function dst end + +""" + weight(e) + +Return the weight of edge `e`. +""" +function weight end + +""" + reverse(e) + +Return a new edge with the same data as edge `e` but inverted source and destination. +""" +Base.reverse + +## Interface test + +""" + check_edge_interface(E) + +Check that objects of type `E` can be used as edges of a graph. +""" +function check_edge_interface(::Type{E}) where {E<:AbstractEdge} + V = eltype(E) + if !check_comparable_interface(V) + return false + elseif !check_comparable_interface(E) + return false + elseif !hasmethod(src, (E,)) + @warn "Missing method GraphsBase.src(::$E)" + return false + elseif !hasmethod(dst, (E,)) + @warn "Missing method GraphsBase.dst(::$E)" + return false + elseif !hasmethod(weight, (E,)) + @warn "Missing method GraphsBase.weight(::$E)" + return false + elseif !hasmethod(Base.reverse, (E,)) + @warn "Missing method Base.reverse(::$E)" + return false + else + return true + end +end + +function check_edge_interface(::Type{E}) where {E} + @warn "$E must be a subtype of AbstractEdge" + return false +end diff --git a/src/interface/abstractgraph.jl b/src/interface/abstractgraph.jl new file mode 100644 index 0000000..7514f83 --- /dev/null +++ b/src/interface/abstractgraph.jl @@ -0,0 +1,108 @@ +""" + AbstractGraph{V,E} + +An abstract type representing a graph with vertices of type `V` and edges of type `E`. + +A subtype `G <: AbstractGraph` satisfies this interface if `E` satisfies the `AbstractEdge` interface, and if the following methods are implemented: + +- `is_directed(::Type{G})` +- `vertices(g::G)` +- `out_edges(g::G, v)` +- `in_edges(g::G, v)` +""" +abstract type AbstractGraph{V,E<:AbstractEdge} end + +## Element types + +""" + eltype(g) + eltype(G) + +Return the type of the vertices of a graph `g` / a graph type `G`. +""" +Base.eltype(::Type{<:AbstractGraph{V,E}}) where {V,E} = V +Base.eltype(::G) where {G<:AbstractGraph} = eltype(G) + +""" + edgetype(g) + edgetype(G) + +Return the type of the edges of a graph `g` / a graph type `G`. +""" +edgetype(::Type{<:AbstractGraph{V,E}}) where {V,E} = E +edgetype(::G) where {G<:AbstractGraph} = edgetype(G) + +""" + weighttype(g) + weighttype(G) + +Return the type of the edge weights of a graph `g` / a graph type `G`. +""" +weighttype(::Type{<:AbstractGraph{V,E}}) where {V,E} = weighttype(E) +weighttype(::G) where {G<:AbstractGraph} = weighttype(G) + +## Interface functions + +""" + is_directed(g) + is_directed(G) + +Return `true` if the graph `g` / graph type `G` is a directed graph. +""" +function is_directed end + +is_directed(::G) where {G<:AbstractGraph} = is_directed(G) + +@traitdef IsDirected{G<:AbstractGraph} +@traitimpl IsDirected{G} < -is_directed(G) + +""" + vertices(g) + +Return an iterable containing the vertices of `g`. +""" +function vertices end + +""" + out_edges(g, u) + +Return an iterable containing the edges of a graph `g` going out of vertex `u`. +""" +function out_edges end + +""" + in_edges(g, v) + +Return an iterable containing the edges of a graph `g` going into vertex `v`. +""" +function in_edges end + +## Interface test + +""" + check_graph_interface(G) + +Check that objects of type `G` can be used as graphs. +""" +function check_graph_interface(::Type{G}) where {G<:AbstractGraph} + V, E = eltype(G), edgetype(G) + if !check_edge_interface(E) + return false + elseif !hasmethod(vertices, (G,)) + @warn "Missing method `GraphsBase.vertices(::$E)`" + return false + elseif !hasmethod(out_edges, (G, V)) + @warn "Missing method `GraphsBase.out_edges(::$E, ::$V)`" + return false + elseif !hasmethod(in_edges, (G, V)) + @warn "Missing method `GraphsBase.in_edges(::$E, ::$V)`" + return false + else + return true + end +end + +function check_graph_interface(::Type{G}) where {G} + @warn "$G must be a subtype of AbstractGraph" + return false +end diff --git a/src/interface/modification.jl b/src/interface/modification.jl new file mode 100644 index 0000000..55c33c9 --- /dev/null +++ b/src/interface/modification.jl @@ -0,0 +1,27 @@ +""" + add_vertex!(g, v) + +Add the vertex `v` to the graph `g`. +""" +function add_vertex! end + +""" + rm_vertex!(g, v) + +Remove the vertex `v` from the graph `g`. +""" +function rm_vertex! end + +""" + add_edge!(g, e) + +Add the edge `e` to the graph `g`. +""" +function add_edge! end + +""" + rm_edge!(g, e) + +Remove the edge `e` from the graph `g`. +""" +function rm_edge! end diff --git a/src/interface/optional.jl b/src/interface/optional.jl new file mode 100644 index 0000000..1c6db3d --- /dev/null +++ b/src/interface/optional.jl @@ -0,0 +1,96 @@ +## Counting + +""" + nv(g) + +Count the number of vertices in `g`. +""" +nv(g::AbstractGraph) = length(vertices(g)) + +""" + ne(g) + +Count the number of edges in `g`. +""" +ne(g::AbstractGraph) = length(edges(g)) + +## Has stuff + +""" + has_vertex(g, v) + +Return `true` if the graph `g` contains the vertex `v`. +""" +has_vertex(g::AbstractGraph, v) = v in vertices(g) + +""" + has_edge(g, e) + +Return `true` if the graph `g` contains the edge `e` (not just an edge with the same source and destination). +""" +has_edge(g::AbstractGraph, e::AbstractEdge) = e in edges(g) + +""" + has_edge(g, u, v) + +Return `true` if the graph `g` contains an edge from vertex `u` to vertex `v`. +""" +has_edge(g::AbstractGraph, u, v) = v in outneighbors(g, u) + +""" + has_self_loops(g) + +Return `true` if `g` has any edge from a vertex to itself. +""" +function has_self_loops(g::AbstractGraph) + return nv(g) == 0 ? false : any(v -> has_edge(g, v, v), vertices(g)) +end + +## Edge iterators + +""" + edges(g) + +Return an iterable containing the edges of `g`. +""" +edges(g::AbstractGraph) = (e for v in vertices(g) for e in out_edges(g, v)) + +""" + edges(g, u, v) + +Return an iterable containing the edges of a graph `g` +going our of `u` and into `v`. +""" +edges(g::AbstractGraph, u, v) = (e for e in outedges(g, u) if dst(e) == v) + +## Neighbor iterators + +""" + in_neighbors(g, v) + +Return an iterable containing all neighbors connected to vertex `v` by an incoming edge. +""" +in_neighbors(g::AbstractGraph, v) = unique(src(e) for e in inedges(g, v)) + +""" + out_neighbors(g, u) + +Return an iterable containing all neighbors connected to vertex `u` by an outgoing edge. +""" +out_neighbors(g::AbstractGraph, u) = unique(dst(e) for e in outedges(g, u)) + +## Containers + +""" + create_vertex_container(g, ::Type{K}) + +Return a new container with element type `K` that can be indexed by the vertices of 'g'. +""" +create_vertex_container(::AbstractGraph{V}, ::Type{K}) where {V,K} = Dict{V,K}() + +""" + create_edge_container(g, ::Type{K}) + +Return a new container with element type `K` that can be indexed by the edges of 'g'. +""" +create_edge_container(::AbstractGraph{V,E}, ::Type{K}) where {V,E,K} = Dict{E,K}() diff --git a/src/interface/utils.jl b/src/interface/utils.jl new file mode 100644 index 0000000..4c739a4 --- /dev/null +++ b/src/interface/utils.jl @@ -0,0 +1,25 @@ +""" + check_comparable_interface(T) + +Check that objects of type `T` can be hashed and compared. + +This is true if the following methods are implemented: + +- `Base.hash(t::T, h::UInt)` +- `Base.isless(t1::T, t2::T)` +- `Base.:(==)(t1::T, t2::T)` +""" +function check_comparable_interface(::Type{T}) where {T} + if !hasmethod(Base.hash, (T, UInt)) + @warn "Missing method `Base.hash(::$T, ::UInt)`" + return false + elseif !hasmethod(Base.isless, (T, T)) + @warn "Missing method `Base.isless(::$T, ::$T)`" + return false + elseif !hasmethod(Base.:(==), (T, T)) + @warn "Missing method `Base.:(==)(::$T, ::$T)`" + return false + else + return true + end +end diff --git a/src/utils.jl b/src/utils.jl deleted file mode 100644 index 7bde964..0000000 --- a/src/utils.jl +++ /dev/null @@ -1,73 +0,0 @@ -""" - 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 vertices(g) - funci = f(g, i) - if comparison(funci, value) - value = funci - end - end - return value -end - -# """ -# insorted(item, collection) - -# Return true if `item` is in sorted collection `collection`. - -# ### Implementation Notes -# Does not verify that `collection` is sorted. -# """ -# function insorted(item, collection) -# index = searchsortedfirst(collection, item) -# @inbounds return (index <= length(collection) && collection[index] == item) -# end - -# """ -# findall!(A, B) - -# Set the `B[1:|I|]` to `I` where `I` is the set of indices `A[I]` returns true. - -# Assumes `length(B) >= |I|`. -# """ -# function findall!(A::Union{BitArray{1},Vector{Bool}}, B::Vector{T}) where {T<:Integer} -# len = 0 -# @inbounds for (i, a) in enumerate(A) -# if a -# len += 1 -# B[len] = i -# end -# end -# return B -# end - -""" - deepcopy_adjlist(adjlist::Vector{Vector{T}}) - -Internal utility function for copying adjacency lists. -On adjacency lists this function is more efficient than `deepcopy` for two reasons: -- As of Julia v1.0.2, `deepcopy` is not typestable. -- `deepcopy` needs to track all references when traversing a recursive data structure - in order to ensure that references to the same location do need get assigned to - different locations in the copy. Because we can assume that all lists in our - adjacency list are different, we don't need to keep track of them. -If `T` is not a bitstype (e.g. `BigInt`), we use the standard `deepcopy`. -""" -function deepcopy_adjlist(adjlist::Vector{Vector{T}}) where {T} - isbitstype(T) || return deepcopy(adjlist) - - result = Vector{Vector{T}}(undef, length(adjlist)) - @inbounds for (i, list) in enumerate(adjlist) - result_list = Vector{T}(undef, length(list)) - for (j, item) in enumerate(list) - result_list[j] = item - end - result[i] = result_list - end - - return result -end diff --git a/test/SimpleGraphs/SimpleGraphs.jl b/test/SimpleGraphs/SimpleGraphs.jl new file mode 100644 index 0000000..dca2e04 --- /dev/null +++ b/test/SimpleGraphs/SimpleGraphs.jl @@ -0,0 +1,6 @@ +using GraphsBase +using GraphsBase.SimpleGraphs +using Test + +@test check_graph_interface(SimpleGraph{Int}) +@test check_graph_interface(SimpleDiGraph{Int}) diff --git a/test/SimpleWeightedGraphs/SimpleWeightedGraphs.jl b/test/SimpleWeightedGraphs/SimpleWeightedGraphs.jl new file mode 100644 index 0000000..28b9559 --- /dev/null +++ b/test/SimpleWeightedGraphs/SimpleWeightedGraphs.jl @@ -0,0 +1,6 @@ +using GraphsBase +using GraphsBase.SimpleWeightedGraphs +using Test + +@test check_graph_interface(SimpleWeightedGraph{Int,Float64}) +@test check_graph_interface(SimpleWeightedDiGraph{Int,Float64}) diff --git a/test/interface/abstractedge.jl b/test/interface/abstractedge.jl new file mode 100644 index 0000000..8e06c82 --- /dev/null +++ b/test/interface/abstractedge.jl @@ -0,0 +1,6 @@ +using GraphsBase +using GraphsBase.SimpleGraphs +using Test + +@test check_edge_interface(SimpleEdge{Int}) +@test !check_edge_interface(Tuple{Int,Int}) diff --git a/test/interface/abstractgraph.jl b/test/interface/abstractgraph.jl new file mode 100644 index 0000000..4287777 --- /dev/null +++ b/test/interface/abstractgraph.jl @@ -0,0 +1,5 @@ +using GraphsBase +using GraphsBase.SimpleGraphs +using Test + +@test !check_graph_interface(Any) diff --git a/test/interface/utils.jl b/test/interface/utils.jl new file mode 100644 index 0000000..2fb0a15 --- /dev/null +++ b/test/interface/utils.jl @@ -0,0 +1,7 @@ +using GraphsBase: check_comparable_interface +using Test + +@test check_comparable_interface(Int) +@test check_comparable_interface(Symbol) +@test check_comparable_interface(String) +@test !check_comparable_interface(Any) diff --git a/test/runtests.jl b/test/runtests.jl index 8b9306f..a25375f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,5 +22,21 @@ DocMeta.setdocmeta!(GraphsBase, :DocTestSetup, :(using GraphsBase); recursive=tr JET.test_package(GraphsBase; target_modules=(GraphsBase,)) end end - # Put the actual tests here + @testset "Interface" begin + @testset "Utils" begin + include("interface/utils.jl") + end + @testset "AbstractEdge" begin + include("interface/abstractedge.jl") + end + @testset "AbstractGraph" begin + include("interface/abstractgraph.jl") + end + end + @testset "SimpleGraphs" begin + include("SimpleGraphs/SimpleGraphs.jl") + end + @testset "SimpleWeightedGraphs" begin + include("SimpleWeightedGraphs/SimpleWeightedGraphs.jl") + end end From 685b90d89dbc26f447663f6cfc32439d92915e38 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:06:42 +0200 Subject: [PATCH 6/8] Ignore Manifest --- .gitignore | 2 +- docs/Manifest.toml | 254 --------------------------------------------- 2 files changed, 1 insertion(+), 255 deletions(-) delete mode 100644 docs/Manifest.toml diff --git a/.gitignore b/.gitignore index 96b9f5b..95731a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ *.jl.cov *.jl.mem /Manifest.toml -# /docs/Manifest.toml +/docs/Manifest.toml /docs/build/ diff --git a/docs/Manifest.toml b/docs/Manifest.toml deleted file mode 100644 index ff98fca..0000000 --- a/docs/Manifest.toml +++ /dev/null @@ -1,254 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.9.3" -manifest_format = "2.0" -project_hash = "4ab590d1fea183f22c4d344fdfc560e0145fca75" - -[[deps.ANSIColoredPrinters]] -git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" -uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" -version = "0.0.1" - -[[deps.AbstractTrees]] -git-tree-sha1 = "faa260e4cb5aba097a73fab382dd4b5819d8ec8c" -uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" -version = "0.4.4" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" -version = "1.1.1" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.5+0" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.3" - -[[deps.Documenter]] -deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "Dates", "DocStringExtensions", "Downloads", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "Test", "Unicode"] -git-tree-sha1 = "f667b805e90d643aeb1ca70189827f991a7cc115" -uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "1.1.0" - -[[deps.Downloads]] -deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" - -[[deps.FileWatching]] -uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" - -[[deps.GraphsBase]] -deps = ["DocStringExtensions", "SimpleTraits", "SparseArrays"] -path = ".." -uuid = "ad2ac648-372e-45be-9d57-a550431b71c3" -version = "0.1.0-DEV" - -[[deps.IOCapture]] -deps = ["Logging", "Random"] -git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6" -uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.3" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" - -[[deps.LazilyInitializedFields]] -git-tree-sha1 = "410fe4739a4b092f2ffe36fcb0dcc3ab12648ce1" -uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" -version = "1.2.1" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.3" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "7.84.0+0" - -[[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.10.2+0" - -[[deps.Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" - -[[deps.LinearAlgebra]] -deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[deps.MacroTools]] -deps = ["Markdown", "Random"] -git-tree-sha1 = "9ee1618cbf5240e6d4e0371d6f24065083f60c48" -uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -version = "0.5.11" - -[[deps.Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[deps.MarkdownAST]] -deps = ["AbstractTrees", "Markdown"] -git-tree-sha1 = "e8513266815200c0c8f522d6d44ffb5e9b366ae4" -uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" -version = "0.1.1" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+0" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.10.11" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.2.0" - -[[deps.OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.21+4" - -[[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.7.2" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.9.2" - -[[deps.PrecompileTools]] -deps = ["Preferences"] -git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" -uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.0" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.4.1" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[deps.Random]] -deps = ["SHA", "Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[deps.RegistryInstances]] -deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] -git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" -uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" -version = "0.1.0" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.SimpleTraits]] -deps = ["InteractiveUtils", "MacroTools"] -git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" -uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" -version = "0.9.4" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[deps.SparseArrays]] -deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] -uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "5.10.1+6" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.3" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" -version = "1.10.0" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[deps.Zlib_jll]] -deps = ["Libdl"] -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+0" - -[[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+0" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.48.0+0" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+0" From 4202619225feea00e11853b04e9dc99434514fd4 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:08:46 +0200 Subject: [PATCH 7/8] Minimize divergence --- docs/make.jl | 1 - docs/src/implementations.md | 5 ----- docs/src/index.md | 6 +----- docs/src/interface.md | 5 ----- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index c2a66b2..7ce6ca4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -8,7 +8,6 @@ makedocs(; authors="JuliaGraphs contributors", sitename="GraphsBase.jl", format=Documenter.HTML(; - repolink="https://github.com/JuliaGraphs/GraphsBase.jl", prettyurls=get(ENV, "CI", "false") == "true", canonical="https://juliagraphs.org/GraphsBase.jl", assets=String[], diff --git a/docs/src/implementations.md b/docs/src/implementations.md index 0b70c50..e89bce8 100644 --- a/docs/src/implementations.md +++ b/docs/src/implementations.md @@ -11,8 +11,3 @@ Modules = [GraphsBase.SimpleGraphs] ```@autodocs Modules = [GraphsBase.SimpleWeightedGraphs] ``` - -## Index - -```@index -``` diff --git a/docs/src/index.md b/docs/src/index.md index 36f812d..2c10368 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,11 +4,7 @@ CurrentModule = GraphsBase # GraphsBase -## API reference - -```@autodocs -Modules = [GraphsBase] -``` +Documentation for [GraphsBase](https://github.com/JuliaGraphs/GraphsBase.jl). ## Index diff --git a/docs/src/interface.md b/docs/src/interface.md index 928695e..8e4dcfa 100644 --- a/docs/src/interface.md +++ b/docs/src/interface.md @@ -59,8 +59,3 @@ GraphsBase.check_comparable_interface GraphsBase.check_edge_interface GraphsBase.check_graph_interface ``` - -## Index - -```@index -``` From c001ac97ade36219a6d54ce076053c53229bf3df Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:22:51 +0200 Subject: [PATCH 8/8] Add GraphsBase docstring --- docs/src/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/index.md b/docs/src/index.md index 2c10368..f5c88d7 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -6,6 +6,10 @@ CurrentModule = GraphsBase Documentation for [GraphsBase](https://github.com/JuliaGraphs/GraphsBase.jl). +```@docs +GraphsBase +``` + ## Index ```@index