Skip to content

Commit 5a2c8fd

Browse files
etiennedeggdallesimonschoelly
authored
rework of weighted graphs API, fixes #6 and Graphs#42 (#14)
I removed all methods using internal fields from the abstract functions. I fixed the issue #6 occurring when removing an edge. It now delete the coefficient (set the coefficient to a structural zero), instead of just zeroing it. It is done more efficiently than by calling dropzeros! and is as efficient as adding a new edge (outside for modifying a weight). Now, the non edges are always structural zeros, so technically, we could allow setting zero weight edges. I did not permit that as it would probably be breaking, and because we can't add zeros from the weights matrix constructor, as zeros are mapped to structural zeros. Review needed please. --------- Co-authored-by: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Co-authored-by: Simon Schölly <sischoel@gmail.com>
1 parent 2ad22df commit 5a2c8fd

File tree

5 files changed

+122
-49
lines changed

5 files changed

+122
-49
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "SimpleWeightedGraphs"
22
uuid = "47aef6b3-ad0c-573a-a1e2-d07658019622"
3-
version = "1.2.2"
3+
version = "1.3.0"
44

55
[deps]
66
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"

src/SimpleWeightedGraphs.jl

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,11 @@ convert(::Type{SparseMatrixCSC{T, U}}, g::AbstractSimpleWeightedGraph) where T<:
5959

6060
### INTERFACE
6161

62-
nv(g::AbstractSimpleWeightedGraph{T, U}) where T where U = T(size(g.weights, 1))
62+
nv(g::AbstractSimpleWeightedGraph{T, U}) where T where U = T(size(weights(g), 1))
6363
vertices(g::AbstractSimpleWeightedGraph{T, U}) where T where U = one(T):nv(g)
6464
eltype(x::AbstractSimpleWeightedGraph{T, U}) where T where U = T
6565
weighttype(x::AbstractSimpleWeightedGraph{T, U}) where T where U = U
6666

67-
has_edge(g::AbstractSimpleWeightedGraph{T, U}, e::AbstractSimpleWeightedEdge) where T where U =
68-
g.weights[dst(e), src(e)] != zero(U)
69-
7067
# handles single-argument edge constructors such as pairs and tuples
7168
has_edge(g::AbstractSimpleWeightedGraph{T, U}, x) where T where U = has_edge(g, edgetype(g)(x))
7269
add_edge!(g::AbstractSimpleWeightedGraph{T, U}, x) where T where U = add_edge!(g, edgetype(g)(x))
@@ -88,42 +85,14 @@ function rem_edge!(g::AbstractSimpleWeightedGraph{T, U}, u::Integer, v::Integer)
8885
rem_edge!(g, edgetype(g)(T(u), T(v), one(U)))
8986
end
9087

91-
@doc_str """
92-
rem_vertex!(g::AbstractSimpleWeightedGraph, v)
93-
94-
Remove the vertex `v` from graph `g`. Return false if removal fails
95-
(e.g., if vertex is not in the graph); true otherwise.
96-
97-
### Implementation Notes
98-
This operation has to be performed carefully if one keeps external
99-
data structures indexed by edges or vertices in the graph, since
100-
internally the removal results in all vertices with indices greater than `v`
101-
being shifted down one.
102-
"""
103-
function rem_vertex!(g::AbstractSimpleWeightedGraph, v::Integer)
104-
v in vertices(g) || return false
105-
n = nv(g)
106-
107-
newweights = g.weights[1:nv(g) .!= v, :]
108-
newweights = newweights[:, 1:nv(g) .!= v]
109-
110-
g.weights = newweights
111-
return true
112-
end
113-
114-
function outneighbors(g::AbstractSimpleWeightedGraph, v::Integer)
115-
mat = g.weights
116-
return view(mat.rowval, mat.colptr[v]:mat.colptr[v+1]-1)
117-
end
118-
119-
get_weight(g::AbstractSimpleWeightedGraph, u::Integer, v::Integer) = g.weights[v, u]
88+
get_weight(g::AbstractSimpleWeightedGraph, u::Integer, v::Integer) = weights(g)[v, u]
12089

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

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

126-
copy(g::T) where T <: AbstractSimpleWeightedGraph = T(copy(g.weights))
95+
copy(g::T) where T <: AbstractSimpleWeightedGraph = T(copy(weights(g)))
12796

12897

12998
const SimpleWeightedGraphEdge = SimpleWeightedEdge
@@ -136,6 +105,21 @@ include("persistence.jl")
136105
const WGraph = SimpleWeightedGraph
137106
const WDiGraph = SimpleWeightedDiGraph
138107

108+
109+
# return the index in nzval of mat[i, j]
110+
# we assume bounds are already checked
111+
# see https://github.com/JuliaSparse/SparseArrays.jl/blob/fa547689947fadd6c2f3d09ddfcb5f26536f18c8/src/sparsematrix.jl#L2492 for implementation
112+
@inbounds function _get_nz_index!(mat::SparseMatrixCSC, i::Integer, j::Integer)
113+
# r1 and r2 are start and end of the column
114+
r1 = Int(mat.colptr[j])
115+
r2 = Int(mat.colptr[j+1]-1)
116+
(r1 > r2) && return 0 # column is empty so we have a non structural zero
117+
# search if i correspond to a stored value
118+
indx = searchsortedfirst(mat.rowval, i, r1, r2, Base.Forward)
119+
((indx > r2) || (mat.rowval[indx] != i)) && return 0
120+
return indx
121+
end
122+
139123
SimpleWeightedDiGraph(g::SimpleWeightedGraph) = SimpleWeightedDiGraph(copy(g.weights))
140124
function SimpleWeightedDiGraph{T, U}(g::SimpleWeightedGraph) where {T<:Integer, U<:Real}
141125
return SimpleWeightedDiGraph(SparseMatrixCSC{U, T}(copy(g.weights)))

src/simpleweighteddigraph.jl

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ SimpleWeightedDiGraph{T,U}(g::SimpleWeightedDiGraph) where {T <: Integer, U <: R
4040

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

43+
function has_edge(g::SimpleWeightedDiGraph, u::Integer, v::Integer)
44+
(u vertices(g) && v vertices(g)) || return false
45+
_get_nz_index!(g.weights, v, u) != 0 # faster than Base.isstored
46+
end
47+
48+
has_edge(g::SimpleWeightedDiGraph, e::AbstractEdge) =
49+
has_edge(g, src(e), dst(e))
50+
4351
function SimpleWeightedDiGraph{T,U}(n::Integer = 0) where {T<:Integer, U<:Real}
4452
weights = spzeros(U, T, T(n), T(n))
4553
return SimpleWeightedDiGraph{T, U}(weights)
@@ -73,7 +81,7 @@ function SimpleWeightedDiGraph(g::Graphs.AbstractGraph{T}, x::U) where {U <: Rea
7381
end
7482

7583
# DiGraph(srcs, dsts, weights)
76-
function SimpleWeightedDiGraph(i::AbstractVector{T}, j::AbstractVector{T}, v::AbstractVector{U}; combine = +) where T<:Integer where U<:Real
84+
function SimpleWeightedDiGraph(i::AbstractVector{T}, j::AbstractVector{T}, v::AbstractVector{U}; combine = +) where {T<:Integer, U<:Real}
7785
m = max(maximum(j), maximum(i))
7886
SimpleWeightedDiGraph{T, U}(sparse(j, i, v, m, m, combine), permute=false)
7987
end
@@ -85,6 +93,10 @@ edgetype(::SimpleWeightedDiGraph{T, U}) where T<:Integer where U<:Real = SimpleW
8593
edges(g::SimpleWeightedDiGraph) = (SimpleWeightedEdge(x[2], x[1], x[3]) for x in zip(findnz(g.weights)...))
8694
weights(g::SimpleWeightedDiGraph) = g.weights'
8795

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

90102
# add_edge! will overwrite weights.
@@ -105,13 +117,41 @@ function add_edge!(g::SimpleWeightedDiGraph, e::SimpleWeightedGraphEdge)
105117
return true
106118
end
107119

108-
function rem_edge!(g::SimpleWeightedDiGraph, e::SimpleWeightedGraphEdge)
109-
has_edge(g, e) || return false
110-
U = weighttype(g)
111-
@inbounds g.weights[dst(e), src(e)] = zero(U)
120+
rem_edge!(g::SimpleWeightedDiGraph, e::AbstractEdge) =
121+
rem_edge!(g, src(e), dst(e))
122+
123+
function rem_edge!(g::SimpleWeightedDiGraph{T}, u::Integer, v::Integer) where {T}
124+
(u vertices(g) && v vertices(g)) || return false
125+
w = g.weights
126+
indx = _get_nz_index!(w, v, u) # get the index in nzval
127+
indx == 0 && return false # the edge does not exist
128+
@view(w.colptr[(u+one(u)):end]) .-= T(1) # there is one value less in column u
129+
# we remove the stored value
130+
deleteat!(w.rowval, indx)
131+
deleteat!(w.nzval, indx)
132+
112133
return true
113134
end
114135

136+
@doc_str """
137+
rem_vertex!(g::SimpleWeightedDiGraph, v)
138+
139+
Remove the vertex `v` from graph `g`. Return false if removal fails
140+
(e.g., if vertex is not in the graph); true otherwise.
141+
142+
### Implementation Notes
143+
This operation has to be performed carefully if one keeps external
144+
data structures indexed by edges or vertices in the graph, since
145+
internally the removal results in all vertices with indices greater than `v`
146+
being shifted down one.
147+
"""
148+
function rem_vertex!(g::SimpleWeightedDiGraph, v::Integer)
149+
v in vertices(g) || return false
150+
n = nv(g)
151+
all_except_v = (1:n) .!= v
152+
g.weights = g.weights[all_except_v, all_except_v]
153+
return true
154+
end
115155

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

@@ -124,7 +164,7 @@ is_directed(::Type{<:SimpleWeightedDiGraph}) = true
124164
125165
Equivalent to g[src(e), dst(e)].
126166
"""
127-
function Base.getindex(g::SimpleWeightedDiGraph{T, U}, e::AbstractEdge, ::Val{:weight}) where {T, U}
167+
function Base.getindex(g::SimpleWeightedDiGraph, e::AbstractEdge, ::Val{:weight})
128168
return g.weights[dst(e), src(e)]
129169
end
130170

@@ -133,6 +173,6 @@ end
133173
134174
Return the weight of edge (i, j).
135175
"""
136-
function Base.getindex(g::SimpleWeightedDiGraph{T, U}, i::Integer, j::Integer, ::Val{:weight}) where {T, U}
176+
function Base.getindex(g::SimpleWeightedDiGraph, i::Integer, j::Integer, ::Val{:weight})
137177
return g.weights[j, i]
138178
end

src/simpleweightedgraph.jl

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ end
2020

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

23+
function has_edge(g::SimpleWeightedGraph, u::Integer, v::Integer)
24+
u vertices(g) && v vertices(g) || return false
25+
_get_nz_index!(g.weights, v, u) != 0 # faster than Base.isstored
26+
end
27+
28+
has_edge(g::SimpleWeightedGraph, e::AbstractEdge) =
29+
has_edge(g, src(e), dst(e))
30+
2331
function nselfloop(g::SimpleWeightedGraph)
2432
n = 0
2533
for i in axes(g.weights, 1)
@@ -111,6 +119,11 @@ edgetype(::SimpleWeightedGraph{T, U}) where {T<:Integer, U<:Real} = SimpleWeight
111119

112120
edges(g::SimpleWeightedGraph) = (SimpleWeightedEdge(x[1], x[2], x[3]) for x in zip(findnz(triu(g.weights))...))
113121
weights(g::SimpleWeightedGraph) = g.weights
122+
123+
function outneighbors(g::SimpleWeightedGraph, v::Integer)
124+
mat = g.weights
125+
return view(mat.rowval, mat.colptr[v]:mat.colptr[v+1]-1)
126+
end
114127
inneighbors(g::SimpleWeightedGraph, x...) = outneighbors(g, x...)
115128

116129
# add_edge! will overwrite weights.
@@ -132,11 +145,44 @@ function add_edge!(g::SimpleWeightedGraph, e::SimpleWeightedGraphEdge)
132145
return true
133146
end
134147

135-
function rem_edge!(g::AbstractSimpleWeightedGraph, e::SimpleWeightedGraphEdge)
136-
has_edge(g, e) || return false
137-
U = weighttype(g)
138-
@inbounds g.weights[dst(e), src(e)] = zero(U)
139-
@inbounds g.weights[src(e), dst(e)] = zero(U)
148+
rem_edge!(g::SimpleWeightedGraph, e::AbstractEdge) =
149+
rem_edge!(g, src(e), dst(e))
150+
151+
function rem_edge!(g::SimpleWeightedGraph{T, U}, u::Integer, v::Integer) where {T<:Integer, U<:Real}
152+
(u vertices(g) && v vertices(g)) || return false
153+
w = g.weights
154+
indx_uv = _get_nz_index!(w, u, v) # get the index in nzval
155+
indx_uv == 0 && return false # the edge does not exist
156+
@view(w.colptr[(v+one(v)):end]) .-= T(1) # there is one value less in column v
157+
# we remove the stored value
158+
deleteat!(w.rowval, indx_uv)
159+
deleteat!(w.nzval, indx_uv)
160+
(u == v) && return true
161+
# same for the reverse edge
162+
indx_vu = _get_nz_index!(w, v, u)
163+
@view(w.colptr[(u+one(u)):end]) .-= T(1)
164+
deleteat!(w.rowval, indx_vu)
165+
deleteat!(w.nzval, indx_vu)
166+
return true
167+
end
168+
169+
@doc_str """
170+
rem_vertex!(g::SimpleWeightedGraph, v)
171+
172+
Remove the vertex `v` from graph `g`. Return false if removal fails
173+
(e.g., if vertex is not in the graph); true otherwise.
174+
175+
### Implementation Notes
176+
This operation has to be performed carefully if one keeps external
177+
data structures indexed by edges or vertices in the graph, since
178+
internally the removal results in all vertices with indices greater than `v`
179+
being shifted down one.
180+
"""
181+
function rem_vertex!(g::SimpleWeightedGraph, v::Integer)
182+
v in vertices(g) || return false
183+
n = nv(g)
184+
all_except_v = (1:n) .!= v
185+
g.weights = g.weights[all_except_v, all_except_v]
140186
return true
141187
end
142188

@@ -145,10 +191,11 @@ end
145191

146192
is_directed(::Type{<:SimpleWeightedGraph}) = false
147193

148-
function Base.getindex(g::SimpleWeightedGraph{T, U}, e::AbstractEdge, ::Val{:weight}) where {T, U}
194+
195+
function Base.getindex(g::SimpleWeightedGraph, e::AbstractEdge, ::Val{:weight})
149196
return g.weights[src(e), dst(e)]
150197
end
151198

152-
function Base.getindex(g::SimpleWeightedGraph{T, U}, i::Integer, j::Integer, ::Val{:weight}) where {T, U}
199+
function Base.getindex(g::SimpleWeightedGraph, i::Integer, j::Integer, ::Val{:weight})
153200
return g.weights[i, j]
154201
end

test/simpleweightedgraph.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ using SimpleWeightedGraphs
7474
@test @inferred(ne(g)) == 3
7575

7676
@test @inferred(rem_edge!(gc, 1, 2)) && @inferred(!has_edge(gc, 1, 2))
77+
@test @inferred(inneighbors(gc, 2)) == @inferred(outneighbors(gc, 2)) == @inferred(neighbors(gc,2)) == [3]
7778
ga = @inferred(copy(g))
7879
@test @inferred(rem_vertex!(ga, 2)) && ne(ga) == 1
7980
@test @inferred(!rem_vertex!(ga, 10))
@@ -142,6 +143,7 @@ using SimpleWeightedGraphs
142143

143144
@test @inferred(!rem_edge!(gc, 2, 1))
144145
@test @inferred(rem_edge!(gc, 1, 2)) && @inferred(!has_edge(gc, 1, 2))
146+
@test @inferred(outneighbors(gc, 1)) == @inferred(neighbors(gc, 1)) == Int[]
145147
ga = @inferred(copy(g))
146148
@test @inferred(rem_vertex!(ga, 2)) && ne(ga) == 1
147149
@test @inferred(!rem_vertex!(ga, 10))

0 commit comments

Comments
 (0)