From 448250e34edd8e6581e8d6881cc54af75ec29c9e Mon Sep 17 00:00:00 2001 From: etienneINSA Date: Thu, 31 Aug 2023 08:54:55 +0200 Subject: [PATCH 1/3] 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/3] 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/3] 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"