Skip to content

Commit 0b99400

Browse files
authored
Improve docs and style (#38)
- Add docstrings everywhere - Clean up code style to activate CI style check - Rework the README into a tutorial page - Stress performance aspects
1 parent 8fc9557 commit 0b99400

20 files changed

+1055
-578
lines changed

docs/Manifest.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
julia_version = "1.8.5"
44
manifest_format = "2.0"
5-
project_hash = "bdbda428ff9bda3e6edacec7c2836953b8b12fea"
5+
project_hash = "09b59749909e1f918f05aece3dff03fef67a9e88"
66

77
[[deps.ANSIColoredPrinters]]
88
git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c"

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
34
SimpleWeightedGraphs = "47aef6b3-ad0c-573a-a1e2-d07658019622"

docs/make.jl

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
using SimpleWeightedGraphs
1+
using Graphs
22
using Documenter
3+
using SimpleWeightedGraphs
34

4-
DocMeta.setdocmeta!(SimpleWeightedGraphs, :DocTestSetup, :(using SimpleWeightedGraphs); recursive=true)
5+
DocMeta.setdocmeta!(
6+
SimpleWeightedGraphs, :DocTestSetup, :(using SimpleWeightedGraphs); recursive=true
7+
)
58

69
makedocs(;
710
modules=[SimpleWeightedGraphs],
@@ -14,15 +17,9 @@ makedocs(;
1417
edit_link="master",
1518
assets=String[],
1619
),
17-
pages=[
18-
"Home" => "index.md",
19-
"API reference" => "api.md",
20-
],
20+
pages=["Home" => "index.md", "Tutorial" => "tutorial.md", "API reference" => "api.md"],
2121
linkcheck=true,
2222
strict=true,
2323
)
2424

25-
deploydocs(;
26-
repo="github.com/JuliaGraphs/SimpleWeightedGraphs.jl",
27-
devbranch="master",
28-
)
25+
deploydocs(; repo="github.com/JuliaGraphs/SimpleWeightedGraphs.jl", devbranch="master")

docs/src/index.md

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,20 @@ CurrentModule = SimpleWeightedGraphs
66

77
Documentation for [SimpleWeightedGraphs](https://github.com/JuliaGraphs/SimpleWeightedGraphs.jl).
88

9-
109
## Quick start
1110

12-
```julia
13-
using Graphs, SimpleWeightedGraphs
14-
15-
g = SimpleWeightedGraph(3) # or use `SimpleWeightedDiGraph` for directed graphs
16-
add_edge!(g, 1, 2, 0.5)
17-
add_edge!(g, 2, 3, 0.8)
18-
add_edge!(g, 1, 3, 2.0)
19-
20-
# get weight of edge from vertex 1 to vertex 2
21-
get_weight(g, 1, 2)
22-
23-
# find the shortest path from vertex 1 to vertex 3 taking weights into account.
24-
enumerate_paths(dijkstra_shortest_paths(g, 1), 3)
25-
3-element Array{Int64,1}:
26-
1
27-
2
28-
3
29-
30-
# reweight the edge from 1 to 2
31-
add_edge!(g, 1, 2, 1.6)
32-
33-
# rerun the shortest path calculation from 1 to 3
34-
enumerate_paths(dijkstra_shortest_paths(g, 1), 3)
35-
2-element Array{Int64,1}:
36-
1
37-
3
38-
39-
# it's possible to build the graph from arrays of sources, destinations and weights
40-
sources = [1,2,1]
41-
destinations = [2,3,3]
42-
weights = [0.5, 0.8, 2.0]
43-
g = SimpleWeightedGraph(sources, destinations, weights)
44-
45-
# the combine keyword handles repeated pairs (sum by default)
46-
g = SimpleWeightedGraph([1,2,1], [2,1,2], [1,1,1]; combine = +)
47-
g.weights[2,1] == g.weights[1,2] == 3 # true
48-
49-
# WARNING: unexpected results might occur with non-associative combine functions
50-
51-
# notice that weights are indexed by [destination, source]
52-
s = SimpleWeightedDiGraph([1,2,1], [2,1,2], [1,1,1]; combine = +)
53-
s.weights[1,2] == 1 # true
54-
s.weights[2,1] == 2 # true
55-
```
11+
This package defines two new graph types: [`SimpleWeightedGraph`](@ref) and [`SimpleWeightedDiGraph`](@ref).
12+
See the tutorial to discover what you can do with them.
13+
Also refer to the [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl) package for more complex algorithms.
5614

5715
## Caveats
5816

59-
Please pay attention to the fact that zero-weight edges are discarded by `add_edge!`.
60-
This is due to the way the graph is stored (a sparse matrix). A possible workaround
61-
is to [set a very small weight instead](https://stackoverflow.com/questions/48977068/how-to-add-free-edge-to-graph-in-lightgraphs-julia/48994712#48994712).
17+
Because `SimpleWeighted(Di)Graph`s are stored in sparse matrices, they have two major flaws:
18+
19+
- Iteratively adding or removing vertices or edges is not very efficient. Building the graph in one go from a list of edge sources, destinations and weights is much faster.
20+
21+
- Zero-weight edges are discarded by `add_edge!`. A possible workaround is to [set a very small weight instead](https://stackoverflow.com/questions/48977068/how-to-add-free-edge-to-graph-in-lightgraphs-julia/48994712#48994712).
22+
23+
## Alternatives
6224

63-
Note that adding or removing vertices or edges from these graph types is not particularly performant. See [MetaGraphsNext.jl](https://github.com/JuliaGraphs/MetaGraphsNext.jl) or [MetaGraphs.jl](https://github.com/JuliaGraphs/MetaGraphs.jl) for possible alternatives.
25+
If your graphs have more than just edge weights to store, take a look at [MetaGraphsNext.jl](https://github.com/JuliaGraphs/MetaGraphsNext.jl) or [MetaGraphs.jl](https://github.com/JuliaGraphs/MetaGraphs.jl) for more complex formats.

docs/src/tutorial.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
```@meta
2+
CurrentModule = SimpleWeightedGraphs
3+
```
4+
5+
# Tutorial
6+
7+
```jldoctest tuto
8+
julia> using Graphs, SimpleWeightedGraphs
9+
```
10+
11+
Here's how to construct an undirected graph (use `SimpleWeightedDiGraph` for directed graphs):
12+
13+
```jldoctest tuto
14+
julia> g = SimpleWeightedGraph(3)
15+
{3, 0} undirected simple Int64 graph with Float64 weights
16+
17+
julia> add_edge!(g, 1, 2, 0.5);
18+
19+
julia> add_edge!(g, 2, 3, 0.8);
20+
21+
julia> add_edge!(g, 1, 3, 2.0);
22+
```
23+
24+
Get the weight of edge from vertex 1 to vertex 2:
25+
26+
```jldoctest tuto
27+
julia> get_weight(g, 1, 2)
28+
0.5
29+
```
30+
31+
Find the shortest path from vertex 1 to vertex 3, taking weights into account:
32+
33+
```jldoctest tuto
34+
julia> enumerate_paths(dijkstra_shortest_paths(g, 1), 3)
35+
3-element Vector{Int64}:
36+
1
37+
2
38+
3
39+
```
40+
41+
Reweight the edge from 1 to 2:
42+
43+
```jldoctest tuto
44+
julia> add_edge!(g, 1, 2, 1.6);
45+
```
46+
47+
Rerun the shortest path calculation from 1 to 3:
48+
49+
```jldoctest tuto
50+
julia> enumerate_paths(dijkstra_shortest_paths(g, 1), 3)
51+
2-element Vector{Int64}:
52+
1
53+
3
54+
```
55+
56+
It's possible (and faster) to build the graph from arrays of sources, destinations and weights:
57+
58+
```jldoctest tuto
59+
julia> sources = [1, 2, 1];
60+
61+
julia> destinations = [2, 3, 3];
62+
63+
julia> weights = [0.5, 0.8, 2.0];
64+
65+
julia> g = SimpleWeightedGraph(sources, destinations, weights)
66+
{3, 3} undirected simple Int64 graph with Float64 weights
67+
```
68+
69+
The combine keyword handles repeated pairs (sum by default).
70+
Unexpected results might occur with non-associative combine functions.
71+
72+
```jldoctest tuto
73+
julia> g = SimpleWeightedGraph([1,2,1], [2,1,2], [1,1,1]; combine = +)
74+
{2, 1} undirected simple Int64 graph with Int64 weights
75+
76+
julia> g.weights[2,1] == g.weights[1,2] == 3
77+
true
78+
```
79+
80+
Notice that weights are indexed by `[destination, source]` internally:
81+
82+
```jldoctest tuto
83+
julia> s = SimpleWeightedDiGraph([1,2,1], [2,1,2], [1,1,1]; combine = +);
84+
85+
julia> s.weights[1,2] == 1
86+
true
87+
88+
julia> s.weights[2,1] == 2
89+
true
90+
```

src/SimpleWeightedGraphs.jl

Lines changed: 29 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,40 @@
1+
"""
2+
SimpleWeightedGraphs
3+
4+
A package for graphs with edge weights, stored as sparse adjacency matrices.
5+
"""
16
module SimpleWeightedGraphs
27

3-
using Graphs
48
using LinearAlgebra
59
using Markdown
6-
using SparseArrays
7-
8-
import Base:
9-
convert, eltype, show, ==, Pair, Tuple, copy, length, issubset, zero
10-
11-
import Graphs:
12-
_NI, AbstractGraph, AbstractEdge, AbstractEdgeIter,
13-
src, dst, edgetype, nv, ne, vertices, edges, is_directed,
14-
add_vertex!, add_edge!, rem_vertex!, rem_edge!,
15-
has_vertex, has_edge, inneighbors, outneighbors,
16-
indegree, outdegree, degree, has_self_loops, num_self_loops,
17-
18-
add_vertices!, adjacency_matrix, laplacian_matrix, weights,
19-
connected_components, cartesian_product,
20-
21-
AbstractGraphFormat, loadgraph, loadgraphs, savegraph,
22-
pagerank, induced_subgraph
23-
24-
export
25-
AbstractSimpleWeightedGraph,
26-
AbstractSimpleWeightedEdge,
27-
SimpleWeightedEdge,
28-
SimpleWeightedGraph,
29-
SimpleWeightedGraphEdge,
30-
SimpleWeightedDiGraph,
31-
SimpleWeightedDiGraphEdge,
32-
weight,
33-
weighttype,
34-
get_weight,
35-
WGraph,
36-
WDiGraph,
37-
SWGFormat,
38-
degree_matrix
39-
10+
using SparseArrays: SparseMatrixCSC, sparse, spzeros, nnz, findnz, spdiagm, nzrange
11+
12+
using Graphs: Graphs
13+
using Graphs: AbstractGraph, AbstractEdge, AbstractEdgeIter, AbstractGraphFormat
14+
using Graphs: SimpleGraph, SimpleDiGraph
15+
using Graphs: src, dst
16+
using Graphs: edgetype, is_directed, nv, ne, vertices, edges
17+
using Graphs: add_vertex!, add_vertices!, add_edge!, rem_vertex!, rem_edge!
18+
using Graphs: has_vertex, has_edge, inneighbors, outneighbors
19+
using Graphs: indegree, outdegree, degree, has_self_loops, num_self_loops
20+
using Graphs: adjacency_matrix, laplacian_matrix, weights
21+
using Graphs: connected_components, cartesian_product, induced_subgraph, pagerank
22+
using Graphs: loadgraph, loadgraphs, savegraph
23+
using Graphs: _NI
24+
25+
export AbstractSimpleWeightedGraph, AbstractSimpleWeightedEdge
26+
export SimpleWeightedGraph, SimpleWeightedDiGraph
27+
export SimpleWeightedEdge, SimpleWeightedGraphEdge, SimpleWeightedDiGraphEdge
28+
export WGraph, WDiGraph, SWGFormat
29+
export weight, weighttype, get_weight, degree_matrix
30+
31+
include("utils.jl")
4032
include("simpleweightededge.jl")
41-
42-
"""
43-
AbstractSimpleWeightedGraph
44-
45-
An abstract type representing a simple graph structure.
46-
AbstractSimpleWeightedGraphs must have the following elements:
47-
- weightmx::AbstractSparseMatrix{Real}
48-
"""
49-
abstract type AbstractSimpleWeightedGraph{T<:Integer,U<:Real} <: AbstractGraph{T} end
50-
51-
function show(io::IO, g::AbstractSimpleWeightedGraph{T, U}) where T where U
52-
dir = is_directed(g) ? "directed" : "undirected"
53-
print(io, "{$(nv(g)), $(ne(g))} $dir simple $T graph with $U weights")
54-
end
55-
56-
# conversion to SparseMatrixCSC
57-
convert(::Type{SparseMatrixCSC{T, U}}, g::AbstractSimpleWeightedGraph) where T<:Real where U<:Integer = SparseMatrixCSC{T, U}(g.weights)
58-
59-
60-
### INTERFACE
61-
62-
nv(g::AbstractSimpleWeightedGraph{T, U}) where T where U = T(size(weights(g), 1))
63-
vertices(g::AbstractSimpleWeightedGraph{T, U}) where T where U = one(T):nv(g)
64-
eltype(x::AbstractSimpleWeightedGraph{T, U}) where T where U = T
65-
weighttype(x::AbstractSimpleWeightedGraph{T, U}) where T where U = U
66-
67-
# handles single-argument edge constructors such as pairs and tuples
68-
has_edge(g::AbstractSimpleWeightedGraph{T, U}, x) where T where U = has_edge(g, edgetype(g)(x))
69-
add_edge!(g::AbstractSimpleWeightedGraph{T, U}, x) where T where U = add_edge!(g, edgetype(g)(x))
70-
71-
# handles two-argument edge constructors like src,dst
72-
has_edge(g::AbstractSimpleWeightedGraph, x, y) = has_edge(g, edgetype(g)(x, y, 0))
73-
add_edge!(g::AbstractSimpleWeightedGraph, x, y) = add_edge!(g, edgetype(g)(x, y, 1))
74-
add_edge!(g::AbstractSimpleWeightedGraph, x, y, z) = add_edge!(g, edgetype(g)(x, y, z))
75-
76-
function issubset(g::T, h::T) where T<:AbstractSimpleWeightedGraph
77-
(gmin, gmax) = extrema(vertices(g))
78-
(hmin, hmax) = extrema(vertices(h))
79-
return (hmin <= gmin <= gmax <= hmax) && issubset(edges(g), edges(h))
80-
end
81-
82-
has_vertex(g::AbstractSimpleWeightedGraph, v::Integer) = v in vertices(g)
83-
84-
function rem_edge!(g::AbstractSimpleWeightedGraph{T, U}, u::Integer, v::Integer) where {T, U}
85-
rem_edge!(g, edgetype(g)(T(u), T(v), one(U)))
86-
end
87-
88-
get_weight(g::AbstractSimpleWeightedGraph, u::Integer, v::Integer) = weights(g)[v, u]
89-
90-
zero(g::T) where T<:AbstractSimpleWeightedGraph = T()
91-
92-
# TODO: manipulte SparseMatrixCSC directly
93-
add_vertex!(g::AbstractSimpleWeightedGraph) = add_vertices!(g, 1)
94-
95-
copy(g::T) where T <: AbstractSimpleWeightedGraph = T(copy(weights(g)))
96-
97-
98-
const SimpleWeightedGraphEdge = SimpleWeightedEdge
99-
const SimpleWeightedDiGraphEdge = SimpleWeightedEdge
33+
include("abstractsimpleweightedgraph.jl")
10034
include("simpleweighteddigraph.jl")
10135
include("simpleweightedgraph.jl")
36+
include("conversion.jl")
10237
include("overrides.jl")
10338
include("persistence.jl")
10439

105-
const WGraph = SimpleWeightedGraph
106-
const WDiGraph = SimpleWeightedDiGraph
107-
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-
123-
SimpleWeightedDiGraph(g::SimpleWeightedGraph) = SimpleWeightedDiGraph(copy(g.weights))
124-
function SimpleWeightedDiGraph{T, U}(g::SimpleWeightedGraph) where {T<:Integer, U<:Real}
125-
return SimpleWeightedDiGraph(SparseMatrixCSC{U, T}(copy(g.weights)))
12640
end
127-
128-
SimpleWeightedGraph(g::SimpleWeightedDiGraph) = SimpleWeightedGraph(g.weights .+ g.weights')
129-
130-
function SimpleWeightedGraph{T, U}(g::SimpleWeightedDiGraph) where {T<:Integer, U<:Real}
131-
return SimpleWeightedGraph(SparseMatrixCSC{U, T}(g.weights .+ g.weights'))
132-
end
133-
134-
end # module

0 commit comments

Comments
 (0)