Skip to content
47 changes: 13 additions & 34 deletions src/SimpleWeightedGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,11 @@ convert(::Type{SparseMatrixCSC{T, U}}, g::AbstractSimpleWeightedGraph) where T<:

### INTERFACE

nv(g::AbstractSimpleWeightedGraph{T, U}) where T where U = T(size(g.weights, 1))
nv(g::AbstractSimpleWeightedGraph{T, U}) where T where U = T(size(weights(g), 1))
vertices(g::AbstractSimpleWeightedGraph{T, U}) where T where U = one(T):nv(g)
eltype(x::AbstractSimpleWeightedGraph{T, U}) where T where U = T
weighttype(x::AbstractSimpleWeightedGraph{T, U}) where T where U = U

has_edge(g::AbstractSimpleWeightedGraph{T, U}, e::AbstractSimpleWeightedEdge) where T where U =
g.weights[dst(e), src(e)] != zero(U)

# handles single-argument edge constructors such as pairs and tuples
has_edge(g::AbstractSimpleWeightedGraph{T, U}, x) where T where U = has_edge(g, edgetype(g)(x))
add_edge!(g::AbstractSimpleWeightedGraph{T, U}, x) where T where U = add_edge!(g, edgetype(g)(x))
Expand All @@ -88,42 +85,14 @@ function rem_edge!(g::AbstractSimpleWeightedGraph{T, U}, u::Integer, v::Integer)
rem_edge!(g, edgetype(g)(T(u), T(v), one(U)))
end

@doc_str """
rem_vertex!(g::AbstractSimpleWeightedGraph, v)

Remove the vertex `v` from graph `g`. Return false if removal fails
(e.g., if vertex is not in the graph); true otherwise.

### 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 results in all vertices with indices greater than `v`
being shifted down one.
"""
function rem_vertex!(g::AbstractSimpleWeightedGraph, v::Integer)
v in vertices(g) || return false
n = nv(g)

newweights = g.weights[1:nv(g) .!= v, :]
newweights = newweights[:, 1:nv(g) .!= v]

g.weights = newweights
return true
end

function outneighbors(g::AbstractSimpleWeightedGraph, v::Integer)
mat = g.weights
return view(mat.rowval, mat.colptr[v]:mat.colptr[v+1]-1)
end

get_weight(g::AbstractSimpleWeightedGraph, u::Integer, v::Integer) = g.weights[v, u]
get_weight(g::AbstractSimpleWeightedGraph, u::Integer, v::Integer) = weights(g)[v, u]

zero(g::T) where T<:AbstractSimpleWeightedGraph = T()

# TODO: manipulte SparseMatrixCSC directly
add_vertex!(g::AbstractSimpleWeightedGraph) = add_vertices!(g, 1)

copy(g::T) where T <: AbstractSimpleWeightedGraph = T(copy(g.weights))
copy(g::T) where T <: AbstractSimpleWeightedGraph = T(copy(weights(g)))


const SimpleWeightedGraphEdge = SimpleWeightedEdge
Expand All @@ -136,6 +105,16 @@ include("persistence.jl")
const WGraph = SimpleWeightedGraph
const WDiGraph = SimpleWeightedDiGraph

# we assume bounds are already checked
@inbounds function _get_nz_index!(mat::SparseMatrixCSC, i::Integer, j::Integer)
r1 = Int(mat.colptr[j])
r2 = Int(mat.colptr[j+1]-1)
(r1 > r2) && return 0
indx = searchsortedfirst(mat.rowval, i, r1, r2, Base.Forward)
((indx > r2) || (mat.rowval[indx] != i)) && return 0
return indx
end

SimpleWeightedDiGraph(g::SimpleWeightedGraph) = SimpleWeightedDiGraph(copy(g.weights))
function SimpleWeightedDiGraph{T, U}(g::SimpleWeightedGraph) where {T<:Integer, U<:Real}
return SimpleWeightedDiGraph(SparseMatrixCSC{U, T}(copy(g.weights)))
Expand Down
46 changes: 42 additions & 4 deletions src/simpleweighteddigraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ SimpleWeightedDiGraph{T,U}(g::SimpleWeightedDiGraph) where {T <: Integer, U <: R

ne(g::SimpleWeightedDiGraph) = nnz(g.weights)

function has_edge(g::SimpleWeightedDiGraph, u::Integer, v::Integer)
u ∈ vertices(g) && v ∈ vertices(g) || return false
_get_nz_index!(g.weights, v, u) != 0 # faster than Base.isstored
end

has_edge(g::SimpleWeightedDiGraph, e::AbstractEdge) =
has_edge(g, src(e), dst(e))

function SimpleWeightedDiGraph{T,U}(n::Integer = 0) where {T<:Integer, U<:Real}
weights = spzeros(U, T, T(n), T(n))
return SimpleWeightedDiGraph{T, U}(weights)
Expand Down Expand Up @@ -85,6 +93,10 @@ edgetype(::SimpleWeightedDiGraph{T, U}) where T<:Integer where U<:Real = SimpleW
edges(g::SimpleWeightedDiGraph) = (SimpleWeightedEdge(x[2], x[1], x[3]) for x in zip(findnz(g.weights)...))
weights(g::SimpleWeightedDiGraph) = g.weights'

function outneighbors(g::SimpleWeightedDiGraph, v::Integer)
mat = g.weights
return view(mat.rowval, mat.colptr[v]:mat.colptr[v+1]-1)
end
inneighbors(g::SimpleWeightedDiGraph, v::Integer) = g.weights[v,:].nzind

# add_edge! will overwrite weights.
Expand All @@ -105,13 +117,39 @@ function add_edge!(g::SimpleWeightedDiGraph, e::SimpleWeightedGraphEdge)
return true
end

function rem_edge!(g::SimpleWeightedDiGraph, e::SimpleWeightedGraphEdge)
has_edge(g, e) || return false
U = weighttype(g)
@inbounds g.weights[dst(e), src(e)] = zero(U)
rem_edge!(g::SimpleWeightedDiGraph{T, U}, e::AbstractEdge) where {T<:Integer, U<:Real} =
rem_edge!(g, src(e), dst(e))

function rem_edge!(g::SimpleWeightedDiGraph{T, U}, u::Integer, v::Integer) where {T<:Integer, U<:Real}
u ∈ vertices(g) && v ∈ vertices(g) || return false
w = g.weights
indx = _get_nz_index!(w, v, u)
indx == 0 && return false
@view(w.colptr[u+one(u):end]) .-= T(1)
deleteat!(w.rowval, indx)
deleteat!(w.nzval, indx)

return true
end

@doc_str """
rem_vertex!(g::SimpleWeightedDiGraph, v)

Remove the vertex `v` from graph `g`. Return false if removal fails
(e.g., if vertex is not in the graph); true otherwise.

### 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 results in all vertices with indices greater than `v`
being shifted down one.
"""
function rem_vertex!(g::SimpleWeightedDiGraph, v::Integer)
v in vertices(g) || return false
n = nv(g)
g.weights = g.weights[1:n .!= v, 1:n .!= v]
return true
end

copy(g::SimpleWeightedDiGraph) = SimpleWeightedDiGraph(copy(g.weights'))

Expand Down
53 changes: 48 additions & 5 deletions src/simpleweightedgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ end

ne(g::SimpleWeightedGraph) = (nnz(g.weights) + nselfloop(g)) ÷ 2

function has_edge(g::SimpleWeightedGraph, u::Integer, v::Integer)
u ∈ vertices(g) && v ∈ vertices(g) || return false
_get_nz_index!(g.weights, v, u) != 0 # faster than Base.isstored
end

has_edge(g::SimpleWeightedGraph, e::AbstractEdge) =
has_edge(g, src(e), dst(e))

function nselfloop(g::SimpleWeightedGraph)
n = 0
for i in axes(g.weights, 1)
Expand Down Expand Up @@ -111,6 +119,11 @@ edgetype(::SimpleWeightedGraph{T, U}) where {T<:Integer, U<:Real} = SimpleWeight

edges(g::SimpleWeightedGraph) = (SimpleWeightedEdge(x[1], x[2], x[3]) for x in zip(findnz(triu(g.weights))...))
weights(g::SimpleWeightedGraph) = g.weights

function outneighbors(g::SimpleWeightedGraph, v::Integer)
mat = g.weights
return view(mat.rowval, mat.colptr[v]:mat.colptr[v+1]-1)
end
inneighbors(g::SimpleWeightedGraph, x...) = outneighbors(g, x...)

# add_edge! will overwrite weights.
Expand All @@ -132,11 +145,41 @@ function add_edge!(g::SimpleWeightedGraph, e::SimpleWeightedGraphEdge)
return true
end

function rem_edge!(g::AbstractSimpleWeightedGraph, e::SimpleWeightedGraphEdge)
has_edge(g, e) || return false
U = weighttype(g)
@inbounds g.weights[dst(e), src(e)] = zero(U)
@inbounds g.weights[src(e), dst(e)] = zero(U)
rem_edge!(g::SimpleWeightedGraph{T, U}, e::AbstractEdge) where {T<:Integer, U<:Real} =
rem_edge!(g, src(e), dst(e))

function rem_edge!(g::SimpleWeightedGraph{T, U}, u::Integer, v::Integer) where {T<:Integer, U<:Real}
u ∈ vertices(g) && v ∈ vertices(g) || return false
w = g.weights
indx = _get_nz_index!(w, u, v)
indx == 0 && return false
@view(w.colptr[v+one(v):end]) .-= T(1)
deleteat!(w.rowval, indx)
deleteat!(w.nzval, indx)
(u == v) && return true
indx = _get_nz_index!(w, v, u)
@view(w.colptr[u+one(u):end]) .-= T(1)
deleteat!(w.rowval, indx)
deleteat!(w.nzval, indx)
return true
end

@doc_str """
rem_vertex!(g::SimpleWeightedGraph, v)

Remove the vertex `v` from graph `g`. Return false if removal fails
(e.g., if vertex is not in the graph); true otherwise.

### 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 results in all vertices with indices greater than `v`
being shifted down one.
"""
function rem_vertex!(g::SimpleWeightedGraph, v::Integer)
v in vertices(g) || return false
n = nv(g)
g.weights = g.weights[1:n .!= v, 1:n .!= v]
return true
end

Expand Down
8 changes: 5 additions & 3 deletions test/simpleweightedgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ using SimpleWeightedGraphs
@test @inferred(ne(g)) == 3

@test @inferred(rem_edge!(gc, 1, 2)) && @inferred(!has_edge(gc, 1, 2))
@test @inferred(inneighbors(gc, 2)) == @inferred(outneighbors(gc, 2)) == @inferred(neighbors(gc,2)) == [3]
ga = @inferred(copy(g))
@test @inferred(rem_vertex!(ga, 2)) && ne(ga) == 1
@test @inferred(!rem_vertex!(ga, 10))
Expand Down Expand Up @@ -142,6 +143,7 @@ using SimpleWeightedGraphs

@test @inferred(!rem_edge!(gc, 2, 1))
@test @inferred(rem_edge!(gc, 1, 2)) && @inferred(!has_edge(gc, 1, 2))
@test @inferred(outneighbors(gc, 1)) == @inferred(neighbors(gc, 1)) == Int[]
ga = @inferred(copy(g))
@test @inferred(rem_vertex!(ga, 2)) && ne(ga) == 1
@test @inferred(!rem_vertex!(ga, 10))
Expand Down Expand Up @@ -263,7 +265,7 @@ using SimpleWeightedGraphs
@test_throws BoundsError g[3, 4, Val{:weight}()]
@test_throws MethodError g[1, 2, Val{:wight}()]
add_edge!(g, 1, 2, 5.0)

@test g[1, 2, Val{:weight}()] ≈ 5
if is_directed(G)
@test g[2, 1, Val{:weight}()] ≈ 0
Expand Down Expand Up @@ -292,7 +294,7 @@ using SimpleWeightedGraphs
@test g1 == g2
@test ne(g1) == 5 # 1-2 1-3 2-3 3-4 4-1
@test g1[1, 3, Val{:weight}()] ≈ 2.5

g = SimpleWeightedGraph(cycle_graph(5))
g2 = SimpleWeightedGraph(g)
@test g[1, 3, Val{:weight}()] ≈ 0
Expand All @@ -317,7 +319,7 @@ using SimpleWeightedGraphs
@test dg[2, 1, Val{:weight}()] ≈ 0
add_edge!(dg, 2, 1, 0.6)
g = SimpleWeightedGraph(dg)
@test g[1, 2, Val{:weight}()] ≈ 1.1
@test g[1, 2, Val{:weight}()] ≈ 1.1
@test g[1, 3, Val{:weight}()] ≈ 0
@test g[2, 3, Val{:weight}()] ≈ 0.5
end
Expand Down