Skip to content

Commit

Permalink
Graphs.jl interop: export adjacency_matrix [close #50]
Browse files Browse the repository at this point in the history
  • Loading branch information
tfiers committed Jan 5, 2023
1 parent b553ab4 commit 74880e0
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 58 deletions.
22 changes: 9 additions & 13 deletions docs/src/bg/graphsjl_interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
PkgGraph does not depend on any of the packages from [JuliaGraphs](https://juliagraphs.org/).

However, you can easily convert the list of package dependencies to a type that supports
the [Graphs.jl interface]. You are then able to use the ecosystem's powerful set of graph analysis tools.

Use [`PkgGraph.depgraph`](@ref) and [`PkgGraph.vertices`](@ref) to obtain the graph edges and vertices, respectively.
the [Graphs.jl interface]. You are then able to use the ecosystem's powerful set of graph analysis tools. See [`PkgGraph.as_graphsjl_input`](@ref).

[Graphs.jl interface]: https://juliagraphs.org/Graphs.jl/dev/ecosystem/interface/

Expand All @@ -15,14 +13,14 @@ Use [`PkgGraph.depgraph`](@ref) and [`PkgGraph.vertices`](@ref) to obtain the gr
## Example

For an example of using Graphs.jl functions on a package dependency DAG, see
[`test/JuliaGraphs_interop.jl`][gh], where we analyze the dependency graph
[`test/graphsjl_interop.jl`][gh], where we analyze the dependency graph
of `Tests`:

```@raw html
<img width=400
src="https://github.com/tfiers/PkgGraph.jl/main/docs/img/Test-deps.svg">
```
[gh]: https://github.com/tfiers/PkgGraph.jl/blob/main/test/JuliaGraphs_interop.jl
[gh]: https://github.com/tfiers/PkgGraph.jl/blob/main/test/graphsjl_interop.jl


This is a summary of that file:
Expand All @@ -31,17 +29,15 @@ This is a summary of that file:
using Graphs

edges = PkgGraph.depgraph("Test")
packages = PkgGraph.vertices(edges)

g = DiGraph(length(packages))
packages = PkgGraph.vertices(edges)
node = PkgGraph.node_index(edges)
A = PkgGraph.adjacency_matrix(edges)

# Graphs.jl needs nodes to be integers
nodes = Dict(pkg => i for (i, pkg) in enumerate(packages))
node(pkg) = nodes[pkg]
# Or, more efficiently:
packages, node, A = PkgGraph.as_graphsjl_input(edges)

for (pkg, dep) in edges
add_edge!(g, node(pkg), node(dep))
end
g = DiGraph(A)

@test outdegree(g, node("Test")) == 4
@test indegree(g, node("Test")) == 0
Expand Down
10 changes: 9 additions & 1 deletion docs/src/ref/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ Internals

```@docs
depgraph
vertices
packages_in_active_manifest
```

Expand Down Expand Up @@ -42,3 +41,12 @@ webapps
```@docs
Options
```

## Graphs.jl conversion

```@docs
vertices
node_index
adjacency_matrix
as_graphsjl_input
```
12 changes: 10 additions & 2 deletions src/internals/Internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ export depgraph,
should_be_included,
is_jll,
is_in_stdlib,
STDLIB,
vertices
STDLIB

include("graphsjl.jl")
export vertices,
node_index,
adjacency_matrix,
as_graphsjl_input,
as_int_pairs,
EdgeList,
Edge

include("dot.jl")
export to_dot_str,
Expand Down
36 changes: 2 additions & 34 deletions src/internals/depgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,42 +110,10 @@ stdlib_packages() = begin
end
packages
end

pkgname(dir) = begin
proj_file = joinpath(dir, "Project.toml")
pkgname(pkgdir) = begin
proj_file = joinpath(pkgdir, "Project.toml")
toml_dict = TOML.parsefile(proj_file)
pkgname = toml_dict["name"]
end

const STDLIB = stdlib_packages()



"""
vertices(edges)
Extract the unique nodes from the given list of edges.
Useful when converting the output of [`depgraph`](@ref) to a `Graphs.jl`
graph. (See the example script in [Working with Graphs.jl](@ref)).
## Example:
```jldoctest
julia> using PkgGraph.Internals
julia> edges = depgraph(:Test);
julia> vertices(edges)
8-element Vector{String}:
"Test"
"InteractiveUtils"
"Markdown"
"Random"
"Base64"
"Logging"
"SHA"
"Serialization"
```
"""
vertices(edges) = [first.(edges); last.(edges)] |> unique!
103 changes: 103 additions & 0 deletions src/internals/graphsjl.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@

"""
as_graphsjl_input(edges)
Obtain the outputs of, respectively
- [`vertices(edges)`](@ref)
- [`node_index(edges)`](@ref)
- [`adjacency_matrix(edges)`](@ref)
..but without unnecessary repeat computation.
Useful when converting the output of [`depgraph`](@ref) to a `Graphs.jl`
graph;\\
See the example script in [Working with Graphs.jl](@ref).
"""
function as_graphsjl_input(edges)
verts = vertices(edges)
f = node_index(verts)
edges = as_int_pairs(edges, f)
A = adjacency_matrix(edges, N = length(verts))
return (;
vertices = verts,
indexof = f,
adjacency_matrix = A,
)
end

"""
vertices(edges)
Extract the unique nodes from the given list of edges.
## Example:
```jldoctest
julia> using PkgGraph.Internals
julia> edges = depgraph(:Test);
julia> vertices(edges)
8-element Vector{String}:
"Test"
"InteractiveUtils"
"Markdown"
"Random"
"Base64"
"Logging"
"SHA"
"Serialization"
```
"""
vertices(edges) = [first.(edges); last.(edges)] |> unique!

const Edge = Union{Pair{I,I}, Tuple{I,I}} where I
const EdgeList = AbstractVector{<:Edge{I}} where I

"""
node_index(edges)
node_index(vertices)
Create a function that returns the index of a given vertex.
This is useful because Graphs.jl requires vertices to be integers.
## Example:
```jldoctest
julia> using PkgGraph.Internals
julia> edges = ["A"=>"B", "B"=>"C"];
julia> node = node_index(edges);
julia> node("C")
3
```
"""
node_index(edges::EdgeList) = node_index(vertices(edges))
node_index(vertices) = begin
nodes = Dict(v => i for (i, v) in enumerate(vertices))
node(v) = nodes[v]
end

as_int_pairs(edges, f = node_index(edges)) =
[f(src) => f(dst) for (src, dst) in edges]

"""
adjacency_matrix(edges)
A square bitmatrix `A` that is `0` everywhere except at `A[i,j]` when there
is a connection _from_ the node with index `i` to the node with index `j`.
"""
adjacency_matrix(edges) = adjacency_matrix(as_int_pairs(edges))
adjacency_matrix(
edges::EdgeList{<:Integer};
N = length(vertices(edges))
) = begin
A = falses(N, N)
for (i, j) in edges
A[i, j] = true
end
A
end
14 changes: 6 additions & 8 deletions test/graphsjl_interop.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@ using Graphs
println(" … done")

edges = PkgGraph.depgraph("Test")
packages = PkgGraph.vertices(edges)

g = DiGraph(length(packages))
packages = PkgGraph.vertices(edges)
node = PkgGraph.node_index(edges)
A = PkgGraph.adjacency_matrix(edges)

# Graphs.jl needs nodes to be integers
nodes = Dict(pkg => i for (i, pkg) in enumerate(packages))
node(pkg) = nodes[pkg]
# Or, more efficiently:
packages, node, A = PkgGraph.as_graphsjl_input(edges)

for (pkg, dep) in edges
add_edge!(g, node(pkg), node(dep))
end
g = DiGraph(A)

@test outdegree(g, node("Test")) == 4
@test indegree(g, node("Test")) == 0
Expand Down
11 changes: 11 additions & 0 deletions test/integration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ end
@test contents(outfile) == contents(expected)
end


@testset "graphsjl" begin

edges = PkgGraph.depgraph("Test")
out = PkgGraph.as_graphsjl_input(edges)
@test out.vertices == PkgGraph.vertices(edges)
@test out.indexof("SHA") == PkgGraph.node_index(edges)("SHA")
@test out.adjacency_matrix == PkgGraph.adjacency_matrix(edges)
end


@testset "dot" begin

@test to_dot_str(:TOML, Options()) ==
Expand Down

0 comments on commit 74880e0

Please sign in to comment.