Skip to content

Graph isomorphism and canonization via NautyGraphs.jl #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[weakdeps]
NautyGraphs = "7509a0a4-015a-4167-b44b-0799a1a2605e"

[extensions]
NautyGraphsExt = "NautyGraphs"

[compat]
Aqua = "0.6"
ArnoldiMethod = "0.4"
Expand All @@ -23,6 +29,7 @@ DataStructures = "0.17, 0.18"
Documenter = "0.27"
Inflate = "0.1.3"
JuliaFormatter = "1"
NautyGraphs = "0.5.0"
SimpleTraits = "0.9"
StableRNGs = "1"
Statistics = "1"
Expand All @@ -36,6 +43,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
NautyGraphs = "7509a0a4-015a-4167-b44b-0799a1a2605e"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Expand All @@ -45,4 +53,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[targets]
test = ["Aqua", "Base64", "DelimitedFiles", "Documenter", "JET", "JuliaFormatter", "LinearAlgebra", "Pkg", "Random", "SparseArrays", "StableRNGs", "Statistics", "Test", "Unitful"]
test = ["Aqua", "Base64", "DelimitedFiles", "Documenter", "JET", "JuliaFormatter", "LinearAlgebra", "NautyGraphs", "Pkg", "Random", "SparseArrays", "StableRNGs", "Statistics", "Test", "Unitful"]
137 changes: 137 additions & 0 deletions ext/NautyGraphsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
module NautyGraphsExt

using Graphs, NautyGraphs
using Graphs.Experimental: AlgNautyGraphs

function Graphs.Experimental.has_induced_subgraphisomorph(

Check warning on line 6 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L6

Added line #L6 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Bool
error(

Check warning on line 13 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L13

Added line #L13 was not covered by tests
"Induced subgraph isomorphims are currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 16 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L16

Added line #L16 was not covered by tests
end
Comment on lines +6 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it is necessary to write these methods. This should just be a MethodError, no need to raise your own errors.


function Graphs.Experimental.has_subgraphisomorph(

Check warning on line 19 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L19

Added line #L19 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Bool
error(

Check warning on line 26 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L26

Added line #L26 was not covered by tests
"Subgraph isomorphims are currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 29 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L29

Added line #L29 was not covered by tests
end

function Graphs.Experimental.has_isomorph(
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Bool
if !isnothing(edge_relation)
error(

Check warning on line 40 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L40

Added line #L40 was not covered by tests
"Edge relations are currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
end
if !isnothing(vertex_relation)
error(

Check warning on line 45 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L45

Added line #L45 was not covered by tests
"Vertex relations are currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
end
return NautyGraph(g1) ≃ NautyGraph(g2)
end

function Graphs.Experimental.canonize!(g::AbstractGraph, ::AlgNautyGraphs)
ng = is_directed(g) ? NautyDiGraph(g) : NautyGraph(g)
perm = convert(Vector{eltype(g)}, NautyGraphs.canonical_permutation(ng))
permute!(g, perm)
return perm
end

function Graphs.Experimental.count_induced_subgraphisomorph(

Check warning on line 59 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L59

Added line #L59 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Int
error(

Check warning on line 66 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L66

Added line #L66 was not covered by tests
"Counting induced subgraph isomorphims is currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 69 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L69

Added line #L69 was not covered by tests
end

function Graphs.Experimental.count_subgraphisomorph(

Check warning on line 72 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L72

Added line #L72 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Int
error(

Check warning on line 79 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L79

Added line #L79 was not covered by tests
"Counting subgraph isomorphims is currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 82 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L82

Added line #L82 was not covered by tests
end

function Graphs.Experimental.count_isomorph(

Check warning on line 85 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L85

Added line #L85 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Int
error(

Check warning on line 92 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L92

Added line #L92 was not covered by tests
"Counting isomorphims is currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 95 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L95

Added line #L95 was not covered by tests
end

function Graphs.Experimental.all_induced_subgraphisomorph(

Check warning on line 98 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L98

Added line #L98 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Channel{Vector{Tuple{eltype(g1),eltype(g2)}}}
error(

Check warning on line 105 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L105

Added line #L105 was not covered by tests
"Generating all induced subgraph isomorphims is currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 108 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L108

Added line #L108 was not covered by tests
end

function Graphs.Experimental.all_subgraphisomorph(

Check warning on line 111 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L111

Added line #L111 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Channel{Vector{Tuple{eltype(g1),eltype(g2)}}}
error(

Check warning on line 118 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L118

Added line #L118 was not covered by tests
"Generating all subgraph isomorphims is currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 121 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L121

Added line #L121 was not covered by tests
end

function Graphs.Experimental.all_isomorph(

Check warning on line 124 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L124

Added line #L124 was not covered by tests
g1::AbstractGraph,
g2::AbstractGraph,
::AlgNautyGraphs;
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Channel{Vector{Tuple{eltype(g1),eltype(g2)}}}
error(

Check warning on line 131 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L131

Added line #L131 was not covered by tests
"Generating all isomorphims is currently not supported by `NautyGraphs`. Please use a different isomorphism algorithm.",
)
return nothing

Check warning on line 134 in ext/NautyGraphsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/NautyGraphsExt.jl#L134

Added line #L134 was not covered by tests
end

end
3 changes: 3 additions & 0 deletions src/Experimental/Experimental.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export description,
# isomorphism
VF2,
vf2,
AlgNautyGraphs,
IsomorphismProblem,
SubGraphIsomorphismProblem,
InducedSubGraphIsomorphismProblem,
could_have_isomorph,
has_isomorph,
all_isomorph,
count_isomorph,
canonize!,
has_induced_subgraphisomorph,
count_induced_subgraphisomorph,
all_induced_subgraphisomorph,
Expand All @@ -25,6 +27,7 @@ description() = "This module contains experimental graph functions."

include("isomorphism.jl")
include("vf2.jl") # Julian implementation of VF2 algorithm
include("nautygraphs.jl")
include("Parallel/Parallel.jl")
include("Traversals/Traversals.jl")
include("ShortestPaths/ShortestPaths.jl")
Expand Down
83 changes: 48 additions & 35 deletions src/Experimental/isomorphism.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,7 @@ function has_induced_subgraphisomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Bool
return has_induced_subgraphisomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(has_induced_subgraphisomorph, (g1, g2, alg)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a breaking change to me. Why is it needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, generally, I try to avoid throwing my own MethodErrors. Structuring the dispatch such that julia is the one the throw MethodErrors usually leads to simpler code (and then error hints can be used for more messages).

end

"""
Expand Down Expand Up @@ -130,9 +128,7 @@ function has_subgraphisomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Bool
return has_subgraphisomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(has_subgraphisomorph, (g1, g2, alg)))
end

"""
Expand All @@ -141,12 +137,14 @@ end
Return `true` if the graph `g1` is isomorphic to `g2`.

### Optional Arguments
- `alg`: The algorithm that is used to find the induced subgraph isomorphism. Can be only
`VF2()` at the moment.
- `alg`: The algorithm that is used to find the induced subgraph isomorphism. Can be
`VF2()` or `AlgNautyGraphs()`, if `NautyGraphs` is installed and imported.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`VF2()` or `AlgNautyGraphs()`, if `NautyGraphs` is installed and imported.
`VF2()` or, if `NautyGraphs` is imported, `AlgNautyGraphs()`.

- `vertex_relation`: A binary function that takes a vertex from `g1` and one from `g2`. An
isomorphism only exists if this function returns `true` for all matched vertices.
isomorphism only exists if this function returns `true` for all matched vertices. Only
works with `VF2()` at the moment.
- `edge_relation`: A binary function that takes an edge from `g1` and one from `g2`. An
isomorphism only exists if this function returns `true` for all matched edges.
isomorphism only exists if this function returns `true` for all matched edges. Only
works with `VF2()` at the moment.

### Examples
```doctest.jl
Expand All @@ -173,9 +171,40 @@ function has_isomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Bool
return has_isomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(has_isomorph, (g1, g2, alg)))
end

"""
canonize!(g, alg::IsomorphismAlgorithm=AlgNautyGraphs())

Permute the vertices of graph `g` into the canonical order defined by the algorithm `alg` and return the permutation.
If graphs `g1` and `g2` are isomorphic, the orders of their vertices will be equal after canonizing them with the same algorithm.

### Optional Arguments
- `alg`: The algorithm that is used to canonize the graph. Can be only be `AlgNautyGraphs()`
at this moment, which requires `NautyGraphs` to be installed and imported.

### Examples
```doctest.jl
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh... I did not know that you can also use doctest.jl as a specifier for doctest!

julia> canonize!(path_graph(3))
[1, 3, 2]

julia> g1 = path_digraph(4)
julia> g2 = path_digraph(4)[[2, 3, 1, 4]]
julia> g1 == g2
false
julia> canonize!(g1)
[4, 2, 3, 1]
julia> canonize!(g2)
[4, 1, 2, 3]
julia> g1 == g2
true
```
### See also
[`has_isomorph`](@ref)
"""
function canonize!(g::AbstractGraph, alg::IsomorphismAlgorithm=AlgNautyGraphs())
throw(MethodError(canonize!, (g, alg)))
end
Comment on lines +206 to 208
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be function canonize! end so that we do not manually throw MethodError. Then we can have a hint that detects the method error and warns that one needs to import NautyGraphs.


"""
Expand Down Expand Up @@ -214,9 +243,7 @@ function count_induced_subgraphisomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Int
return count_induced_subgraphisomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(count_induced_subgraphisomorph, (g1, g2, alg)))
end

"""
Expand Down Expand Up @@ -257,13 +284,7 @@ function count_subgraphisomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Int
return count_subgraphisomorph(
g1::AbstractGraph,
g2::AbstractGraph,
VF2();
vertex_relation=vertex_relation,
edge_relation=edge_relation,
)
throw(MethodError(count_subgraphisomorph, (g1, g2, alg)))
end

"""
Expand Down Expand Up @@ -304,9 +325,7 @@ function count_isomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Int
return count_isomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(count_isomorph, (g1, g2, alg)))
end

"""
Expand Down Expand Up @@ -353,9 +372,7 @@ function all_induced_subgraphisomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Channel{Vector{Tuple{eltype(g1),eltype(g2)}}}
return all_induced_subgraphisomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(all_induced_subgraphisomorph, (g1, g2, alg)))
end

"""
Expand Down Expand Up @@ -404,9 +421,7 @@ function all_subgraphisomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Channel{Vector{Tuple{eltype(g1),eltype(g2)}}}
return all_subgraphisomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(all_subgraphisomorph, (g1, g2, alg)))
end

"""
Expand Down Expand Up @@ -458,7 +473,5 @@ function all_isomorph(
vertex_relation::Union{Nothing,Function}=nothing,
edge_relation::Union{Nothing,Function}=nothing,
)::Channel{Vector{Tuple{eltype(g1),eltype(g2)}}}
return all_isomorph(
g1, g2, alg; vertex_relation=vertex_relation, edge_relation=edge_relation
)
throw(MethodError(all_isomorph, (g1, g2, alg)))
end
8 changes: 8 additions & 0 deletions src/Experimental/nautygraphs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
AlgNautyGraphs

An empty concrete type used to dispatch to [`NautyGraphs`](@ref) isomorphism functions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
An empty concrete type used to dispatch to [`NautyGraphs`](@ref) isomorphism functions.
A "configuration" type used to dispatch to isomorphism algorithms from the `nauty_graphs` C library,
wrapped by the `NautyGraphs.jl` package.

"""
struct AlgNautyGraphs <: IsomorphismAlgorithm end

# The implementation of NautyGraph methods for graph isomorphism is done as a package extension in /ext/NautyGraphsExt.jl
Loading
Loading