From c2a9f04cc426b426c31bcfcad14c3055c6169c3c Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 21 Sep 2022 21:35:08 -0700 Subject: [PATCH] basic search functionality, word problem hacky solution --- Project.toml | 1 - src/categorical_algebra/CSets.jl | 104 ++++++++++- src/categorical_algebra/Categories.jl | 1 + src/categorical_algebra/Diagrams.jl | 34 +++- src/categorical_algebra/FinCats.jl | 257 +++++++++++++++++++++++++- src/graphs/GraphAlgorithms.jl | 41 +++- test/categorical_algebra/Diagrams.jl | 26 ++- test/categorical_algebra/FinCats.jl | 58 ++++-- 8 files changed, 490 insertions(+), 32 deletions(-) diff --git a/Project.toml b/Project.toml index 6c6075bf5..c062b12d3 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,6 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [compat] Colors = "0.12" -CompTime = "0.1" Compose = "0.7, 0.8, 0.9" DataStructures = "0.17, 0.18" GeneralizedGenerated = "0.2, 0.3" diff --git a/src/categorical_algebra/CSets.jl b/src/categorical_algebra/CSets.jl index 31172a563..5b0ba634e 100644 --- a/src/categorical_algebra/CSets.jl +++ b/src/categorical_algebra/CSets.jl @@ -31,7 +31,9 @@ import ..Limits: limit, colimit, universal import ..Subobjects: Subobject, implies, ⟹, subtract, \, negate, ¬, non, ~ import ..Sets: SetOb, SetFunction, TypeSet import ..FinSets: FinSet, FinFunction, FinDomFunction, force, predicate -import ..FinCats: FinDomFunctor, components, is_natural +import ..FinCats: FinDomFunctor, components, is_natural, FinCatGraph, FinCatPresentation, homomorphisms +using ...Graphs +using ..FinCats: normalize, Path # Sets interop ############## @@ -724,6 +726,106 @@ partial_assignments(x::AbstractVector) = in_hom(S, c) = [dom(S,f) => f for f in hom(S) if codom(S,f) == c] out_hom(S, c) = [f => codom(S,f) for f in hom(S) if dom(S,f) == c] + + +""" +Convert a FinFunctor between graph FinCats and add labels from +FinCatPresentations +""" +function grph_fun_to_pres_fun(F, X,Y) + hs = map(F.hom_map) do p + if isempty(p.edges) + return id(ob_generators(Y)[p.src]) + else + compose(hom_generators(Y)[p.edges]) + end + end + FinFunctor(Dict(zip(ob_generators(X), ob_generators(Y)[F.ob_map])), + Dict(zip(hom_generators(X),hs)), X, Y) +end + +# Convert presentation inputs into graph inputs, opposite for outputs +homomorphisms(X::FinCatPresentation, Y::FinCatPresentation; kwargs...) = + [grph_fun_to_pres_fun(F,X,Y) for F in + homomorphisms(FinCatGraph(X),FinCatGraph(Y); kwargs...)] + +""" +Search for FinFunctors between FinCats. I.e. map objects onto objects and +generating morphisms onto paths. Note that there can be infinite paths in a +FinCat with a cyclic underlying graph (although equations make make it finite). +`nmax` restricts the paths found to those which pass over the same object at +most `nmax` times. + +Because morphism equality modulo codomain equations is not implemented, there +may be "duplicate" morphisms in the results. To be safe, we error if the +`monic`/`iso` constraints are used when the codomain has equations. +ob and hom maps can be initialized. Hom maps can be restricted to be a +particular length (e.g. 1 means a generator must map onto another generator, not +a composite). +""" +function homomorphisms(gX::FinCatGraph, gY::FinCatGraph; n_max::Int=3, + monic=false, iso=false,init_obs=nothing, + init_homs=nothing, hom_lens=nothing) + + y_pths = Dict(map(collect(enumerate_paths_cyclic(gY.graph; n_max=n_max))) do (k,v) + k => unique(h->normalize(gY, Path(h, k[1], k[2])), v) + end) + + yGrph = Graph(nv(gY.graph)) + + for (i,j) in Iterators.product(vertices(gY.graph), vertices(gY.graph)) + if !isempty(y_pths[(i, j)]) + add_edge!(yGrph, i, j) + end + end + no,nh = [length(f(gX)) for f in [ob_generators,hom_generators]] + init_obs = isnothing(init_obs) ? fill(nothing,no) : init_obs + init_homs = isnothing(init_homs) ? fill(nothing,nh) : init_homs + hom_lens = isnothing(hom_lens) ? fill(nothing,nh) : hom_lens + res = [] + kwargs = Dict{Symbol,Any}(:initial=>(V=Dict([ + i=>v for (i, v) in enumerate(init_obs) if !isnothing(v)]),)) + if iso kwargs[:iso] = [:V] + elseif monic kwargs[:monic] = [:V] + end + for h in homomorphisms(gX.graph, yGrph; kwargs...) + om = collect(h[:V]) + + pths = map(zip(collect(h[:E]), init_homs, hom_lens)) do (e, ih, hl) + p = y_pths[(src(yGrph,e),tgt(yGrph,e))] + # Apply init_homs and hom_lens constraints to possible paths + if !isnothing(hl) + filter!(z->length(z)==hl, p) + end + if !isnothing(ih) + maybe_e = findfirst(x->(x isa Vector ? x : edges(x)) == e, p) + if isnothing(maybe_e) + p = [] + else + p = [p[maybe_e]] + end + end + return p + end + + for combo in Iterators.product(pths...) # pick a path for each edge in X + hm = map(enumerate(combo)) do (ei,z) + isempty(z) ? id(gY, om[src(gX.graph,ei)]) : z + end + if (monic || iso) && ((length(hm) != length(unique(hm))) || any(isempty,combo)) + continue + elseif iso + error("how to check hom map is surjective?") + end + F = FinFunctor((V=om, E=hm), gX, gY) + if is_functorial(F; check_equations=true) + push!(res, F) + end + end + end + return res +end + # Limits and colimits ##################### diff --git a/src/categorical_algebra/Categories.jl b/src/categorical_algebra/Categories.jl index ea6586502..82908fd01 100644 --- a/src/categorical_algebra/Categories.jl +++ b/src/categorical_algebra/Categories.jl @@ -100,6 +100,7 @@ categories, checking equality of morphisms may involve nontrivial reasoning. is_hom_equal(C::Cat, f, g) = is_hom_equal(f, g) is_hom_equal(f, g) = f == g + # Instances #---------- diff --git a/src/categorical_algebra/Diagrams.jl b/src/categorical_algebra/Diagrams.jl index 99ddf5da5..3003c76ca 100644 --- a/src/categorical_algebra/Diagrams.jl +++ b/src/categorical_algebra/Diagrams.jl @@ -13,7 +13,7 @@ using ..FinCats, ..FreeDiagrams using ..FinCats: mapvals import ..FinCats: force, collect_ob, collect_hom import ..Limits: limit, colimit, universal - +import ..CSets: homomorphisms, is_natural # Data types ############ @@ -159,6 +159,11 @@ function Base.show(io::IO, f::DiagramHom{T}) where T print(io, ")") end +function is_natural(f::DiagramHom) + all(is_functorial,[shape_map(f), diagram.([dom(f), codom(f)])...] + ) && is_natural(diagram_map(f)) +end + # Categories of diagrams ######################## @@ -225,6 +230,33 @@ function compose(f::DiagramHom{T}, F::Functor; kw...) where T compose(f.precomposed_diagram, F; kw...)) end +""" +Diagram morphisms by first finding shape maps and then, for each, +finding a diagram map. +""" +function homomorphisms(X::Diagram{T}, Y::Diagram{T}; n_max::Int=3, monic=false, + iso=false,init_obs=nothing,init_homs=nothing, + hom_lens=nothing) where T + fs = homomorphisms(shape(X),shape(Y)) + + res = [] + for f in fs + codom(f) == shape(Y) || error("SHOULD COMPOSE") + if T == id + hargs = [diagram(X), f ⋅ diagram(Y)] + elseif T == op + hargs = [f ⋅ diagram(Y),diagram(X)] + else + error("$T not supported") + end + nts = homomorphisms(hargs...) + for nt in nts + push!(res, DiagramHom{T}(f, nt.components, X, Y)) + end + end + return res +end + # Limits and colimits ##################### diff --git a/src/categorical_algebra/FinCats.jl b/src/categorical_algebra/FinCats.jl index 441b486c1..796fa94f7 100644 --- a/src/categorical_algebra/FinCats.jl +++ b/src/categorical_algebra/FinCats.jl @@ -14,7 +14,7 @@ export FinCat, FinCatGraph, Path, ob_generator, hom_generator, ob_generator_name, hom_generator_name, ob_generators, hom_generators, equations, is_discrete, is_free, graph, edges, src, tgt, presentation, FinFunctor, FinDomFunctor, is_functorial, collect_ob, collect_hom, force, - FinTransformation, components, is_natural, is_initial + FinTransformation, components, is_natural, is_initial, homomorphisms using StructEquality using Reexport @@ -23,12 +23,12 @@ using DataStructures: IntDisjointSets, in_same_set, num_groups @reexport using ..Categories using ...GAT, ...Present, ...Syntax -import ...Present: equations +import ...Present: equations, generators using ...Theories: ThCategory, ThSchema, ObExpr, HomExpr, AttrExpr, AttrTypeExpr import ...Theories: dom, codom, id, compose, ⋅, ∘ using ...CSetDataStructures, ...Graphs import ...Graphs: edges, src, tgt, enumerate_paths -import ..Categories: CatSize, ob, hom, ob_map, hom_map, component, op +import ..Categories: CatSize, ob, hom, ob_map, hom_map, component, op, is_hom_equal # Categories ############ @@ -259,6 +259,7 @@ See (Spivak, 2014, *Category theory for the sciences*, §4.5). end equations(C::FinCatGraphEq) = C.equations +equations(C::FinCatGraph) = Pair[] function FinCatGraph(g::HasGraph, eqs::AbstractVector) eqs = map(eqs) do eq @@ -270,6 +271,10 @@ function FinCatGraph(g::HasGraph, eqs::AbstractVector) FinCatGraphEq(g, eqs) end + +FinCatGraph(p::FinCatGraph) = p + + # Symbolic categories ##################### @@ -345,6 +350,31 @@ function Base.show(io::IO, C::FinCatPresentation) print(io, ")") end +function FinCatGraph(p::FinCatPresentation) + pres = p.presentation + eqs = map(equations(pres)) do e12 + e1,e2 = map(e12) do e + s, t = [findfirst(==(st), ob_generators(p)) for st in e.type_args] + et = only(typeof(e).parameters) + if et == :compose + return Path(Int[findfirst(==(x), hom_generators(p)) for x in e.args], s, t) + elseif et == :id + i = findfirst(==(e), ob_generators(p)) + return Path(Int[], s, t) + else + Path(Int[findfirst(==(et), hom_generators(p))], s, t) + end + end + return e1 => e2 + end + + g = Graph(length(generators(pres, :Ob))) + hs = generators(pres, :Hom) + add_edges!(g, map(f -> generator_index(pres, first(gat_type_args(f))), hs), + map(f -> generator_index(pres, last(gat_type_args(f))), hs)) + return isempty(eqs) ? FinCatGraph(g) : FinCatGraphEq(g,eqs) +end + # Functors ########## @@ -639,6 +669,145 @@ function Base.show(io::IO, α::FinTransformationMap) print(io, ")") end + + +""" Internal state for backtracking search for FinTransformations between FinCatGraph. +""" +struct BacktrackingState + """ The current assignment, a partially-defined homomorphism of ACSets. """ + assignment::Vector{Union{Nothing,Path}} + """ Depth in search tree at which assignments were made. """ + assignment_depth::Vector{Int} + """ Inverse assignment for monic components or if finding a monomorphism. """ + inv_assignment::Union{Nothing,Vector{Int}} + """ Domain FinCat: the "variables" in the CSP. """ + dom::FinFunctor + """ Codomain FinCat: the "values" in the CSP. """ + codom::FinFunctor + """ Domain FinCat: the "variables" in the CSP. """ + src::FinCatGraph + """ Codomain FinCat: the "values" in the CSP. """ + tgt::FinCatGraph + cd_pths::Dict{Tuple{Int64, Int64}, Vector{Path}} +end + +function homomorphisms(X::FinFunctor, Y::FinFunctor; kw...) + results = FinTransformation[] + backtracking_search(X, Y; kw...) do α + if is_natural(α) + push!(results, deepcopy(α)); + end + return false + end + results +end + +function backtracking_search(f, X::FinFunctor, Y::FinFunctor; n_max=2) + D, CD = dom(X), codom(X) # assume same for Y + Y_pths = Dict(map(collect(enumerate_paths_cyclic(CD.graph; n_max=n_max))) do (k,v) + v_ = [Path(x, k[1], k[2]) for x in v] + k => unique(h->normalize(CD, h), v_) + end) + + # Initialize state variables for search. + assignment = fill(nothing, length(ob_generators(D))) + assignment_depth = zeros(Int, length(ob_generators(D))) + inv_assignment = nothing # unless monic=true ... deepcopy(assignment) + state = BacktrackingState(assignment, assignment_depth, inv_assignment, + X, Y, D, CD, Y_pths) + + # Start the main recursion for backtracking search. + backtracking_search(f, state, 1) +end + +function backtracking_search(f, state::BacktrackingState, depth::Int) where {S} + # Choose the next unassigned element. + x = findfirst(isnothing, state.assignment) + if isnothing(x) + # No unassigned elements remain, so we have a complete assignment. + return f(FinTransformation(state.assignment, state.dom, state.codom)) + end + + # Attempt all assignments of the chosen element. + Y = state.codom + for y in state.cd_pths[(ob_map(state.dom,x), ob_map(state.codom,x))] + assign_elem!(state, depth, x, y) && + backtracking_search(f, state, depth + 1) && + return true + unassign_elem!(state, depth, x) + end + return false +end + + +""" Check whether element x can be assigned to y in current assignment. +""" +function can_assign_elem(state::BacktrackingState, depth, x, y) + # Although this method is nonmutating overall, we must temporarily mutate the + # backtracking state, for several reasons. First, an assignment can be a + # consistent at each individual subpart but not consistent for all subparts + # simultaneously (consider trying to assign a self-loop to an edge with + # distinct vertices). Moreover, in schemas with non-trivial endomorphisms, we + # must keep track of which elements we have visited to avoid looping forever. + ok = assign_elem!(state, depth, x, y) + unassign_elem!(state, depth, x) + return ok +end + +""" Attempt to assign element (c,x) to (c,y) in the current assignment. + +Returns whether the assignment succeeded. Note that the backtracking state can +be mutated even when the assignment fails. +""" +function assign_elem!(state::BacktrackingState, depth, x, y) + y′ = state.assignment[x] + y′ == y && return true # If x is already assigned to y, return immediately. + isnothing(y′) || return false # Otherwise, x must be unassigned. + + if !isnothing(state.inv_assignment) && state.inv_assignment[y] != 0 + # Also, y must unassigned in the inverse assignment. + return false + end + + # Make the assignment and recursively assign subparts. + state.assignment[x] = y + state.assignment_depth[x] = depth + if !isnothing(state.inv_assignment) + state.inv_assignment[y] = x + end + return true +end + +""" Unassign the element (c,x) in the current assignment. +""" +function unassign_elem!(state::BacktrackingState, depth, x) + + state.assignment[x] == 0 && return + assign_depth = state.assignment_depth[x] + @assert assign_depth <= depth + if assign_depth == depth + X = state.dom + if !isnothing(state.inv_assignment) + y = state.assignment[x] + state.inv_assignment[y] = 0 + end + state.assignment[x] = nothing + state.assignment_depth[x] = 0 + end +end + +""" Get assignment pairs from partially specified component of C-set morphism. +""" +partial_assignments(x::AbstractDict) = pairs(x) +partial_assignments(x::AbstractVector) = + ((i,y) for (i,y) in enumerate(x) if !isnothing(y) && y > 0) + +# FIXME: Should these accessors go elsewhere? +in_hom(S, c) = [dom(S,f) => f for f in hom_generators(S) if codom(S,f) == c] +out_hom(S, c) = [f => codom(S,f) for f in hom_generators(S) if dom(S,f) == c] + + + # Initial functors ################## @@ -746,4 +915,86 @@ function make_map(f, xs, ::Type{T}) where T end end +# Word problems + +""" +String rewriting (term rewriting with only unary operators) is an undecidable +problem for the general case of arbitrary generators and equations. +A MetaTheory.jl interop could give a more satisfying approximate solution. + +For now, we repeatedly apply equations as rewrite rules that make a +term smaller or equal size. +""" +function is_hom_equal(C::FinCat, f, g) + is_hom_equal(normalize(C,f), normalize(C,g)) +end + + +term_size(t::Path) = length(edges(t)) + +"""Used to orient equations into rewrite rules""" +function term_size(t) + typ = only(typeof(t).parameters) + if typ == :id + 0 + elseif typ == :generator + 1 + elseif typ == :compose + length(t.args) + else error("is_hom_equal does not support terms of this type: $t") + end +end +"""Apply substitution to a list""" +function sublist(xs, pat, rep) + pat != rep || error("Pattern and replacement must differ") + for i in 1:(length(xs)-(length(pat)-1)) + if xs[i:i+length(pat)-1] == pat + return vcat(xs[1:i-1], rep, xs[i+length(pat):end]) => true + end + end + xs => false +end + +function sub(t::Path,pat,rep) + new_edges, changed = sublist(edges(t), edges(pat), edges(rep)) + return changed ? Path(new_edges, t.src, t.tgt) => true : t => false +end + +"""Substitute pattern in a FreeSchema term""" +function sub(t,pat,rep) + ttyp,pattyp, reptyp = [only(typeof(x).parameters) for x in [t,pat,rep]] + ttyp == :compose || return t => false + pattyp == :compose || error("pattern must be compose $pat") + if reptyp == :id + r = [] + elseif reptyp == :generator + r = [rep] + else + r = rep.args + end + new_args, changed = sublist(t.args, pat.args, r) + if !changed + return t => false + elseif isempty(new_args) + return id(t.type_args[1]) => true + elseif length(new_args) == 1 + return only(new_args) => true + else + return typeof(t)(new_args, t.type_args) => true + end +end +"""Use path equalities to make term as small as possible""" +function normalize(C::FinCat, m) + changed = true + while changed + changed = false + for eqterms in equations(C) + r, l = sort(collect(eqterms); by=term_size) + m, was_changed = sub(m, l, r) + changed |= was_changed + end + end + m +end + end diff --git a/src/graphs/GraphAlgorithms.jl b/src/graphs/GraphAlgorithms.jl index d4dd59542..b3ffcc3ad 100644 --- a/src/graphs/GraphAlgorithms.jl +++ b/src/graphs/GraphAlgorithms.jl @@ -2,7 +2,7 @@ """ module GraphAlgorithms export connected_components, connected_component_projection, connected_component_projection_bfs, - topological_sort, transitive_reduction!, enumerate_paths + topological_sort, transitive_reduction!, enumerate_paths, enumerate_paths_cyclic using DataStructures: Stack, DefaultDict @@ -147,4 +147,43 @@ function enumerate_paths(G::Graph; return res end +""" +Idea: have a DAG <: Graph type that stores the graph as well as +an ordering. That way dispatch could use the correct function. + +Because cyclic graphs have an infinite number of paths, a cap on the the number of loops is required +""" +const Pth = Vector{Int} +function enumerate_paths_cyclic(G::Graph; n_max=2) + ijs = collect(Iterators.product(vertices(G),vertices(G))) + es = Dict([(i,j)=>i==j ? [Int[]] : Pth[] for (i,j) in ijs]) + n,done = 0,false + + """False iff any vertex is visited more than n_max times""" + function count_cycles(p::Vector{Int}) + cnt = zeros(Int, nv(G)) + for e in p + cnt[src(G,e)] += 1 + if cnt[src(G,e)] > n_max return false end + end + return true + end + + while !done + done = true + n += 1 # we now add paths of length n + new_unseen = Dict([ij=>Int[] for ij in ijs]) + for e in edges(G) # try to postcompose this edge w/ len n-1 paths + s, t = src(G,e), tgt(G,e) + for src_v in vertices(G) + for u in filter(u->length(u)==n-1 && count_cycles(u),es[(src_v, s)]) + push!(es[(src_v, t)], [u; [e]]) + done = false + end + end + end + end + return es end + +end # module diff --git a/test/categorical_algebra/Diagrams.jl b/test/categorical_algebra/Diagrams.jl index 2af578c94..85b0bac9f 100644 --- a/test/categorical_algebra/Diagrams.jl +++ b/test/categorical_algebra/Diagrams.jl @@ -1,5 +1,7 @@ module TestDiagrams + using Test +using Revise using Catlab.Theories, Catlab.Graphs, Catlab.CategoricalAlgebra @@ -9,13 +11,10 @@ const SchSGraph = SchSymmetricGraph ########## # Diagram for paths of length 2. -C = FinCat(@acset Graph begin - V = 3 - E = 2 - src = [1,2] - tgt = [3,3] -end) +C = FinCat(@acset Graph begin V = 3; E = 2; src = [1,2]; tgt = [3,3] end) D = FinDomFunctor([:E,:E,:V], [:tgt,:src], C, FinCat(SchSGraph)) + + d = Diagram(D) @test shape(d) == C @test ob_map(d, 3) == SchSGraph[:V] @@ -66,6 +65,21 @@ d = dom(f) @test codom(op(f)) == op(dom(f)) @test op(g)⋅op(f) == op(f⋅g) +# Morphism search +################## +Squarish = FinCatGraph(@acset(Graph, begin + V=4;E=4;src=[1,1,2,3]; tgt=[2,3,4,4] +end), [[[1,3],[2,4]]]) + +Arr = FinCat(path_graph(Graph, 2)) +XF = FinFunctor((V=[1,3], E=[[2]]), Arr, Squarish) +XG = FinFunctor((V=[2,4], E=[[3]]), Arr, Squarish) +@test all(is_functorial,[XF,XG]) +F, G = Diagram.([XF,XG]) +hs = homomorphisms(F,G) + + + # Monads of diagrams #################### diff --git a/test/categorical_algebra/FinCats.jl b/test/categorical_algebra/FinCats.jl index fe44e1e66..1f2247091 100644 --- a/test/categorical_algebra/FinCats.jl +++ b/test/categorical_algebra/FinCats.jl @@ -1,6 +1,7 @@ module TestFinCats -using Test +using Test +using Revise using Catlab, Catlab.Theories, Catlab.CategoricalAlgebra, Catlab.Graphs # Categories on graphs @@ -18,6 +19,9 @@ C = FinCat(g) @test hom_generators(C) == 1:3 @test startswith(sprint(show, C), "FinCat($(Graph)") +# all gens to id (src or tgt) or 3*3*3 possibilities for V map = [1,2] +@test length(homomorphisms(C,C)) == 29 + C_op = op(C) @test C_op isa FinCat @test (ob(C_op, 1), ob_generator(C_op, 1)) == (1, 1) @@ -36,6 +40,9 @@ f = id(D, 2) g = compose(D, 1, 3) @test edges(g) == [1,3] +homs = homomorphisms(D,D) +@test all(is_functorial, homs) + D_op = op(D) @test dom(D_op, 1) == 2 @test codom(D_op, 1) == 1 @@ -45,12 +52,17 @@ D_op = op(D) # Functors between free categories. C = FinCat(parallel_arrows(Graph, 2)) F = FinFunctor((V=[1,4], E=[[1,3], [2,4]]), C, D) +@test length(homomorphisms(F,F))==1 @test dom(F) == C @test codom(F) == D @test is_functorial(F) @test Ob(F) == FinFunction([1,4], FinSet(4)) @test startswith(sprint(show, F), "FinFunctor($([1,4]),") +homs = homomorphisms(C,D; monic=true); +@test F ∈ homs +@test length(homs) == 2 + @test ob_map(F, 2) == 4 @test hom_map(F, 1) == Path(h, [1,3]) @test collect_ob(F) == [1,4] @@ -136,6 +148,15 @@ end @test !is_free(Δ¹) @test startswith(sprint(show, Δ¹), "FinCat(") +SG = FinCat(SchGraph) +@test length(homomorphisms(Δ¹,Δ¹; n_max=0, monic=true))==2 + + +# not tested yet, but have possible swap for obs and homs +SG = FinCat(SchGraph) +@test length(homomorphisms(SG,SG; n_max=2, monic=true))==2 + + # Graph as set-valued functor on a free category. F = FinDomFunctor(path_graph(Graph, 3)) C = dom(F) @@ -204,39 +225,38 @@ G = FinDomFunctor(g) @test ob_map(G, :Weight) == TypeSet(Float64) @test hom_map(G, :weight) == FinDomFunction([0.5, 1.5]) +# FinTransformation search +########################## +Squarish = FinCatGraph(@acset(Graph, begin + V=4; E=5; src=[1,1,1,2,3]; tgt=[2,2,3,4,4] +end), [[[1,4],[3,5]]]) +Arr = FinCat(path_graph(Graph, 2)) +F = FinFunctor((V=[1,3], E=[[3]]), Arr, Squarish) +G = FinFunctor((V=[2,4], E=[[4]]), Arr, Squarish) +FGs = homomorphisms(F,G) +@test only(FGs) == FinTransformation([[1], [5]], F, G) + # Initial functors ################## # Commutative square diagram: with 1→2→4 and 1→3→4 S = FinCat(@acset Graph begin - V = 4 - E = 4 - src = [1,1,2,3] - tgt = [2,3,4,4] + V = 4; E = 4; src = [1,1,2,3]; tgt = [2,3,4,4] end) # Equalizer diagram: 1→2⇉3 T = FinCat(@acset Graph begin - V = 3 - E = 3 - src = [1,2,2] - tgt = [2,3,3] + V = 3; E = 3; src = [1,2,2]; tgt = [2,3,3] end) # Extra bit added to beginning equalizer diagram: 4→1→2⇉3 T2 = FinCat(@acset Graph begin - V = 4 - E = 4 - src = [1,2,2,4] - tgt = [2,3,3,1] + V = 4; E = 4; src = [1,2,2,4]; tgt = [2,3,3,1] end) # Extra bit added to end of equalizer diagram: 1→2⇉3→4 T3 = FinCat(@acset Graph begin - V = 4 - E = 4 - src = [1,2,2,3] - tgt = [2,3,3,4] + V = 4; E = 4; src = [1,2,2,3]; tgt = [2,3,3,4] end) # Opposite square corners folded on top of each other @@ -254,11 +274,11 @@ F4 = FinFunctor([1,2,3], [1,2,3], T, T2) # Same as F1, but there is an additional piece of data in codomain, ignored F5 = FinFunctor([1,2,3], [1,2,3], T, T2) -@test all(is_functorial.([F1,F2,F3,F4])) +@test all(is_functorial.([F1,F2,F3,F4,F5])) @test is_initial(F1) @test !is_initial(F2) @test !is_initial(F3) @test !is_initial(F4) @test !is_initial(F5) -end +end # module