Skip to content
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

Basic nauty integration #893

Open
wants to merge 2 commits into
base: main
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
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85"
MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5"
SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13"
TikzPictures = "37f6aa50-8035-52d0-81c2-5a1d08754b2d"
nauty_jll = "55c6dc9b-343a-50ca-8ff2-b71adb3733d5"

[extensions]
CatlabConvexExt = "Convex"
CatlabDataFramesExt = "DataFrames"
CatlabGraphsExt = "Graphs"
CatlabGraphvizExt = "Graphviz_jll"
CatlabMetaGraphsExt = "MetaGraphs"
CatlabNautyExt = "nauty_jll"
CatlabSCSExt = "SCS"
CatlabTikzPicturesExt = "TikzPictures"

Expand Down
92 changes: 92 additions & 0 deletions ext/CatlabNautyExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
module CatlabNautyExt

using nauty_jll
import Catlab.CategoricalAlgebra.CSets: ACSetTransformation, Multispan,
Multicospan, apex, legs
using Catlab.Theories: ⋅
using ACSets
import ACSets: call_nauty, strhsh, orbits, ngroup, canon, dom, codom
using StructEquality

# Morphisms up to isomorphism
#----------------------------

"""
Given an ACSet X and a canonical element of the iso class, [X], obtain either
a map X -> [X] or (if `inv=true`) [X]->X
"""
function ACSetTransformation(start::ACSet, n::CSetNautyRes; inv::Bool=false)
comps = Dict([k => (inv ? invperm(v) : v) for (k, v) in pairs(canonmap(n))])
args = (inv ? reverse : identity)([start, canon(n)])
ACSetTransformation(args...; comps...)
end

"""
Given an ACSetTransformation A -> B (and canonical isos A→A' and B→B′), we
construct a canonical ACSetTransformation A′→B′.
"""
@struct_hash_equal struct ACSetTransformationNautyRes <: NautyRes
dom::CSetNautyRes
codom::CSetNautyRes
canon::ACSetTransformation # Cache the computation of this
end

"""Optionally pass in the CSetNautyRes for (co)dom if known"""
function call_nauty(f::ACSetTransformation; dom_canon=nothing, codom_canon=nothing)
d, cd = map([dom=>dom_canon, codom=>codom_canon]) do (get, canon)
isnothing(canon) ? call_nauty(get(f)) : canon
end
d′ = ACSetTransformation(dom(f), d; inv=true)
cd′ = ACSetTransformation(codom(f), cd)
ACSetTransformationNautyRes(d, cd, d′⋅f⋅cd′)
end

dom(n::ACSetTransformationNautyRes) = n.dom
codom(n::ACSetTransformationNautyRes) = n.codom

Check warning on line 45 in ext/CatlabNautyExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CatlabNautyExt.jl#L44-L45

Added lines #L44 - L45 were not covered by tests

strhsh(n::ACSetTransformationNautyRes) = strhsh(n.dom) * strhsh(n.codom)
orbits(n::ACSetTransformationNautyRes) = orbits(dom(n)) => orbits(codom(n))
generators(n::ACSetTransformationNautyRes) = generators(dom(n)) => generators(codom(n))
ngroup(n::ACSetTransformationNautyRes) = ngroup(dom(n)) => ngroup(codom(n))

Check warning on line 50 in ext/CatlabNautyExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CatlabNautyExt.jl#L47-L50

Added lines #L47 - L50 were not covered by tests
canon(n::ACSetTransformationNautyRes) = n.canon

# (Co)spans up to isomorphism
#----------------------------

abstract type SpanCospanNautyRes <: NautyRes end

@struct_hash_equal struct MultispanNautyRes <: SpanCospanNautyRes
apex::CSetNautyRes
legs::AbstractVector{ACSetTransformationNautyRes}
end

@struct_hash_equal struct MulticospanNautyRes <: SpanCospanNautyRes
apex::CSetNautyRes
legs::AbstractVector{ACSetTransformationNautyRes}
end

apex(s::SpanCospanNautyRes) = s.apex

Check warning on line 68 in ext/CatlabNautyExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CatlabNautyExt.jl#L68

Added line #L68 was not covered by tests
legs(s::SpanCospanNautyRes) = s.legs
Base.length(s::SpanCospanNautyRes) = length(s.legs)

Check warning on line 70 in ext/CatlabNautyExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CatlabNautyExt.jl#L70

Added line #L70 was not covered by tests

function call_nauty(f::Multispan)
ap = call_nauty(apex(f))
MultispanNautyRes(ap, call_nauty.(f; dom_canon=ap))
end

function call_nauty(f::Multicospan)
ap = call_nauty(apex(f))
MulticospanNautyRes(ap, call_nauty.(f; codom_canon=ap))

Check warning on line 79 in ext/CatlabNautyExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CatlabNautyExt.jl#L77-L79

Added lines #L77 - L79 were not covered by tests
end


strhsh(n::SpanCospanNautyRes) = join(strhsh.(legs(n)))
orbits(n::SpanCospanNautyRes) = [orbits(apex(n)), orbits.(codom.(legs(n)))...]
generators(n::SpanCospanNautyRes) = [generators(apex(n)), generators.(codom.(legs(n)))...]
ngroup(n::SpanCospanNautyRes) = [ngroup(apex(n)), ngroup.(codom.(legs(n)))...]

Check warning on line 86 in ext/CatlabNautyExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CatlabNautyExt.jl#L83-L86

Added lines #L83 - L86 were not covered by tests
canon(n::SpanCospanNautyRes) = Multispan(canon.(legs(n)))

# C-Set diagrams up to isomorphism - TODO
#----------------------------------------

end # module
6 changes: 5 additions & 1 deletion src/categorical_algebra/FreeDiagrams.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
using GATlab
using ...Theories, ...Graphs, ..FinCats
import AlgebraicInterfaces: ob, hom, dom, codom
import ..FinCats: FreeCatGraph, FinDomFunctor, collect_ob, collect_hom
import ..FinCats: FreeCatGraph, FinDomFunctor, collect_ob, collect_hom, force

# Diagram interface
###################
Expand Down Expand Up @@ -161,6 +161,8 @@
Base.eltype(span::Multispan) = eltype(span.legs)
Base.length(span::Multispan) = length(span.legs)

force(span::Multispan) = Multispan(apex(span), force.(legs(span)))

""" Multicospan of morphisms in a category.

A multicospan is like a [`Cospan`](@ref) except that it may have a number of
Expand Down Expand Up @@ -203,6 +205,8 @@
Base.eltype(cospan::Multicospan) = eltype(cospan.legs)
Base.length(cospan::Multicospan) = length(cospan.legs)

force(span::Multicospan) = Multicospan(apex(span), force.(legs(span)))

Check warning on line 208 in src/categorical_algebra/FreeDiagrams.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FreeDiagrams.jl#L208

Added line #L208 was not covered by tests

""" Bundle together legs of a multi(co)span.

For example, calling `bundle_legs(span, SVector((1,2),(3,4)))` on a multispan
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
nauty_jll = "55c6dc9b-343a-50ca-8ff2-b71adb3733d5"

[compat]
Graphs = "^1"
34 changes: 34 additions & 0 deletions test/categorical_algebra/CSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ using Test

using Catlab.Theories, Catlab.Graphs, Catlab.CategoricalAlgebra



@present SchDDS(FreeSchema) begin
X::Ob
Φ::Hom(X,X)
Expand Down Expand Up @@ -796,4 +798,36 @@ rem_part!(p3, :E, 3)
rem_part!(p3, :V, 4)
@test !in_bounds(f)


# Test nauty interface
######################

if !Sys.iswindows()
using nauty_jll

# G1 = 3-cycle, G2 = 3-cycle with extra vertex
G1 = @acset Graph begin V=3; E=3; src=[1,2,3]; tgt=[2,3,1] end;
G1′ = @acset Graph begin V=3; E=3; src=[3,2,1]; tgt=[2,1,3] end;
G2 = @acset Graph begin V=4; E=3; src=[2,3,4]; tgt=[3,4,2] end;
G2′ = @acset Graph begin V=4; E=3; src=[1,2,3]; tgt=[2,3,1] end;
h,h′ = call_nauty.(homomorphism.([G1,G1′],[G2,G2′]));
@test canon(h) == canon(h′)

# Test pullback up to iso
G = @acset Graph begin V=3; E=3; src=[1,1,2]; tgt=[1,2,2] end
f = homomorphism(path_graph(Graph, 3), G; initial=(E=[1,2],))
g = homomorphism(path_graph(Graph, 2), G; initial=(E=[2],))
eq = pullback(f,g);
canon_eq = canon(call_nauty(cone(eq))) |> force

bkwd_path_3 = @acset Graph begin V=3; E=2; src=[3,2]; tgt=[2,1] end
bkwd_path_2 = @acset Graph begin V=2; E=1; src=2; tgt=1 end
bkwd_apex = @acset Graph begin V=3; E=1; src=1; tgt=3 end
L = homomorphisms(bkwd_apex, bkwd_path_3; monic=true, initial=(E=[2],)) |> only
R = homomorphisms(bkwd_apex, bkwd_path_2; initial=(V=[2,2,1],)) |> only
expected = canon(call_nauty(Span(L,R)))
@test expected == canon_eq

end

end # module
Loading