Skip to content
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: 7 additions & 3 deletions src/NautyGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ abstract type AbstractNautyGraph{T} <: AbstractGraph{T} end
include("utils.jl")
include("graphset.jl")
include("densenautygraph.jl")
include("sparsenautygraph.jl")
include("nauty.jl")

const NautyGraph = DenseNautyGraph{false}
const NautyDiGraph = DenseNautyGraph{true}


function __init__()
# global default options to nauty carry a pointer reference that needs to be initialized at runtime
DEFAULTOPTIONS16.dispatch = cglobal((:dispatch_graph, libnauty(UInt16)), Cvoid)
DEFAULTOPTIONS32.dispatch = cglobal((:dispatch_graph, libnauty(UInt32)), Cvoid)
DEFAULTOPTIONS64.dispatch = cglobal((:dispatch_graph, libnauty(UInt64)), Cvoid)
DEFAULTOPTIONS_DENSE16.dispatch = cglobal((:dispatch_graph, libnauty(UInt16)), Cvoid)
DEFAULTOPTIONS_DENSE32.dispatch = cglobal((:dispatch_graph, libnauty(UInt32)), Cvoid)
DEFAULTOPTIONS_DENSE64.dispatch = cglobal((:dispatch_graph, libnauty(UInt64)), Cvoid)
DEFAULTOPTIONS_SPARSE.dispatch = cglobal((:dispatch_sparse, libnauty(UInt64)), Cvoid)
return
end

Expand All @@ -45,6 +48,7 @@ export
NautyGraph,
NautyDiGraph,
DenseNautyGraph,
SparseNautyGraph,
AutomorphismGroup,
labels,
label,
Expand Down
18 changes: 9 additions & 9 deletions src/densenautygraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,24 @@ The graph can be directed (`D = true`) or undirected (`D = false`). If `D = fals
Vertex labels can optionally be specified.
"""
function DenseNautyGraph{D,W}(A::AbstractMatrix; vertex_labels=nothing) where {D,W<:Unsigned}
n, m = size(A)
isequal(n, m) || throw(ArgumentError("Adjacency / distance matrices must be square"))
D || issymmetric(A) || throw(ArgumentError("Adjacency / distance matrices must be symmetric"))
graphset = Graphset{W}(A)
return DenseNautyGraph{D}(graphset; vertex_labels)
end
DenseNautyGraph{D}(A::AbstractMatrix; vertex_labels=nothing) where {D} = DenseNautyGraph{D,UInt}(A; vertex_labels)

function (::Type{G})(g::AbstractGraph) where {G<:AbstractNautyGraph}
ng = G(nv(g))
function (::Type{G})(g::AbstractGraph) where {G<:DenseNautyGraph}
ng = g isa AbstractNautyGraph ? G(nv(g); vertex_labels=labels(g)) : G(nv(g))
for e in edges(g)
add_edge!(ng, e)
!is_directed(g) && is_directed(ng) && add_edge!(ng, reverse(e))
if !is_directed(g) && is_directed(ng)
add_edge!(ng, reverse(e))
end
end
return ng
end
function (::Type{G})(g::AbstractNautyGraph) where {G<:AbstractNautyGraph}
h = invoke(G, Tuple{AbstractGraph}, g)
@views h._labels .= g._labels
h.iscanon = g.iscanon
return h
end

"""
DenseNautyGraph{D}(edge_list::Vector{<:AbstractEdge}; [vertex_labels]) where {D}
Expand All @@ -101,6 +99,8 @@ function DenseNautyGraph{D,W}(edge_list::Vector{<:AbstractEdge}; vertex_labels=n
end
DenseNautyGraph{D}(edge_list::Vector{<:AbstractEdge}; vertex_labels=nothing) where {D} = DenseNautyGraph{D,UInt}(edge_list; vertex_labels)

libnauty(::DenseNautyGraph{D,W}) where {D,W} = libnauty(W)
libnauty(::Type{DenseNautyGraph{D,W}}) where {D,W} = libnauty(W)

Base.copy(g::G) where {G<:DenseNautyGraph} = G(copy(g.graphset), copy(g._labels), g.ne, g.iscanon)
function Base.copy!(dest::G, src::G) where {G<:DenseNautyGraph}
Expand Down
89 changes: 68 additions & 21 deletions src/nauty.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
libnauty(::Type{UInt16}) = nauty_jll.libnautyTS
libnauty(::Type{UInt32}) = nauty_jll.libnautyTW
libnauty(::Type{UInt64}) = nauty_jll.libnautyTL
libnauty(::DenseNautyGraph{D,W}) where {D,W} = libnauty(W)

mutable struct NautyOptions
getcanon::Cint # Warning: setting getcanon to false means that nauty will NOT compute the canonical representative, which may lead to unexpected results.
Expand Down Expand Up @@ -43,12 +42,15 @@ end
return :(NautyOptions(cglobal((:dispatch_graph, $(libnauty(W))), Cvoid); digraph_or_loops, ignorelabels, groupinfo))
end

const DEFAULTOPTIONS16 = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)
const DEFAULTOPTIONS32 = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)
const DEFAULTOPTIONS64 = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)
default_options(::DenseNautyGraph{D,UInt16}) where {D} = DEFAULTOPTIONS16
default_options(::DenseNautyGraph{D,UInt32}) where {D} = DEFAULTOPTIONS32
default_options(::DenseNautyGraph{D,UInt64}) where {D} = DEFAULTOPTIONS64
const DEFAULTOPTIONS_DENSE16 = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)
const DEFAULTOPTIONS_DENSE32 = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)
const DEFAULTOPTIONS_DENSE64 = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)
const DEFAULTOPTIONS_SPARSE = NautyOptions(C_NULL; digraph_or_loops=true, ignorelabels=false, groupinfo=false)

default_options(::DenseNautyGraph{D,UInt16}) where {D} = DEFAULTOPTIONS_DENSE16
default_options(::DenseNautyGraph{D,UInt32}) where {D} = DEFAULTOPTIONS_DENSE32
default_options(::DenseNautyGraph{D,UInt64}) where {D} = DEFAULTOPTIONS_DENSE64
default_options(::SparseNautyGraph) = DEFAULTOPTIONS_SPARSE

mutable struct NautyStatistics
grpsize1::Cdouble
Expand Down Expand Up @@ -76,22 +78,30 @@ struct AutomorphismGroup
# generators::Vector{Vector{Cint}} #TODO: not implemented
end

function _densenauty(g::DenseNautyGraph{D,W}, options::NautyOptions=default_options(g), statistics::NautyStatistics=NautyStatistics()) where {D,W}
function _nauty(g::DenseNautyGraph{D,W}, options::NautyOptions=default_options(g), statistics::NautyStatistics=NautyStatistics()) where {D,W}
# TODO: allow the user to pass pre-allocated arrays for lab, ptn, orbits, canong in a safe way.
n, m = g.graphset.n, g.graphset.m

lab, ptn = vertexlabels2labptn(labels(g))
orbits = zeros(Cint, n)
canong = Graphset{W}(n, m)

_ccall_densenauty(g, lab, ptn, orbits, options, statistics, canong)
_ccall_nauty(g, lab, ptn, orbits, options, statistics, canong)
canonperm = (lab .+= 1)
return canong, canonperm, orbits, statistics
end
function _nauty(g::SparseNautyGraph{D}, options::NautyOptions=default_options(g), statistics::NautyStatistics=NautyStatistics()) where {D}
lab, ptn = vertexlabels2labptn(g.labels)
orbits = zeros(Cint, nv(g))
canong = SparseGraphRep()

_ccall_nauty(g, lab, ptn, orbits, options, statistics, canong)
canonperm = (lab .+= 1)
return canong, canonperm, orbits, statistics
end

@generated function _ccall_densenauty(g::DenseNautyGraph{D,W}, lab, ptn, orbits, options, statistics, canong) where {D,W}
return quote @ccall $(libnauty(W)).densenauty(
@generated function _ccall_nauty(g::DenseNautyGraph{D,W}, lab, ptn, orbits, options, statistics, canong) where {D,W}
return quote @ccall $(libnauty(g)).densenauty(
g.graphset.words::Ref{W},
lab::Ref{Cint},
ptn::Ref{Cint},
Expand All @@ -102,15 +112,49 @@ end
g.graphset.n::Cint,
canong.words::Ref{W})::Cvoid end
end
@generated function _ccall_nauty(g::SparseNautyGraph, lab, ptn, orbits, options, statistics, canong)
return quote
@ccall $(libnauty(g)).sparsenauty(
Ref(g)::Ref{SparseGraphRep},
lab::Ref{Cint},
ptn::Ref{Cint},
orbits::Ref{Cint},
Ref(options)::Ref{NautyOptions},
Ref(statistics)::Ref{NautyStatistics},
Ref(canong)::Ref{SparseGraphRep})::Cvoid end
end

function _sethash!(g::DenseNautyGraph, canong::Graphset, canonperm)
# Base.hash skips elements in arrays of length >= 8192
# Use SHA in these cases
canong_hash = length(canong) >= 8192 ? hash_sha(canong) : hash(canong)
labels_hash = @views length(g.labels) >= 8192 ? hash_sha(g.labels[canonperm]) : hash(g.labels[canonperm])

hashval = hash(labels_hash, canong_hash)
g.hashval = hashval
return
end
function _canonize!(g::DenseNautyGraph, canong::Graphset, canonperm)
copy!(g.graphset, canong)
permute!(g.labels, canonperm)
return
end
function _sethash!(g::SparseNautyGraph, canong::SparseGraphRep, canonperm)
# TODO
return
end
function _canonize!(g::SparseNautyGraph, canong::SparseGraphRep, canonperm)
_unsafe_copyfromsparsegraphrep!(g, canong)
permute!(g.labels, canonperm)
return
end

"""
nauty(g::AbstractNautyGraph, [options::NautyOptions; canonize=false])
nauty(g::AbstractNautyGraph, [options::NautyOptions]; [canonize=false])

Compute a graph `g`'s canonical form and automorphism group.
"""
function nauty(::AbstractNautyGraph, ::NautyOptions; kwargs...) end

function nauty(g::DenseNautyGraph, options::NautyOptions=default_options(g); canonize=false)
function nauty(g::AbstractNautyGraph, options::NautyOptions=default_options(g); canonize=false)
if is_directed(g) && !isone(options.digraph)
error("Nauty options need to match the directedness of the input graph. Make sure to instantiate options with `digraph=true` if the input graph is directed.")
end
Expand All @@ -119,14 +163,17 @@ function nauty(g::DenseNautyGraph, options::NautyOptions=default_options(g); can
error("`options.getcanon` needs to be enabled.")
end

canong, canonperm, orbits, statistics = _densenauty(g, options)
canong, canonperm, orbits, statistics = _nauty(g, options)
# generators = Vector{Cint}[] # TODO: extract generators from nauty call
autg = AutomorphismGroup(statistics.grpsize1 * 10^statistics.grpsize2, orbits)

if canonize
_copycanon!(g, canong, canonperm)
g.iscanon = true
end

# free memory allocated by nauty for sparse graphs
canong isa SparseGraphRep && _free_sparsegraphrep(canong)
return canonperm, autg
end

Expand All @@ -139,7 +186,7 @@ function canonize!(::AbstractNautyGraph) end

function canonize!(g::DenseNautyGraph)
iscanon(g) && return canonical_permutation(g)
canong, canonperm, _ = _densenauty(g)
canong, canonperm, _ = _nauty(g)
_copycanon!(g, canong, canonperm)
return canonperm
end
Expand All @@ -160,7 +207,7 @@ function canonical_permutation(::AbstractNautyGraph) end

function canonical_permutation(g::DenseNautyGraph)
iscanon(g) && return collect(Cint(1):Cint(nv(g))) # to be type stable, this needs to be Cints
_, canonperm, _ = _densenauty(g)
_, canonperm, _ = _nauty(g)
return canonperm
end

Expand All @@ -173,8 +220,8 @@ function is_isomorphic(::AbstractNautyGraph, ::AbstractNautyGraph) end

function is_isomorphic(g::DenseNautyGraph, h::DenseNautyGraph)
iscanon(g) && iscanon(h) && return g == h
canong, permg, _ = _densenauty(g)
canonh, permh, _ = _densenauty(h)
canong, permg, _ = _nauty(g)
canonh, permh, _ = _nauty(h)
return canong == canonh && view(g._labels, permg) == view(h._labels, permh)
end
≃(g::AbstractNautyGraph, h::AbstractNautyGraph) = is_isomorphic(g, h)
Expand All @@ -193,7 +240,7 @@ function canonical_id(g::DenseNautyGraph)
if iscanon(g)
return _SHAhash(g.graphset, g._labels)
else
canong, canonperm, _ = _densenauty(g)
canong, canonperm, _ = _nauty(g)
return _SHAhash(canong, @view g._labels[canonperm])
end
end
Expand Down
Loading
Loading