Skip to content

Commit

Permalink
Merge pull request #544 from epatters/composite-functors
Browse files Browse the repository at this point in the history
Composite functions and functors
  • Loading branch information
epatters committed Nov 2, 2021
2 parents bbb741b + a4f408a commit accf7a9
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 99 deletions.
51 changes: 20 additions & 31 deletions src/categorical_algebra/CSetDataStructures.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module CSetDataStructures
export @acset_type, @abstract_acset_type, @declare_schema, StructACSet, StructCSet,
ACSetTableType
export @acset_type, @abstract_acset_type, @declare_schema, FreeSchema,
StructACSet, StructCSet, ACSetTableType

using MLStyle
using StaticArrays
Expand All @@ -10,10 +10,9 @@ import Tables
@reexport using ..ACSetInterface
using ..IndexUtils
using ...Theories, ...Present, ...Syntax
using ...Theories: SchemaDesc, SchemaDescType, CSetSchemaDescType, SchemaDescTypeType,
ob_num, codom_num, attr, attrtype
@reexport using ...Theories: FreeSchema
using ...Meta: strip_lines
using ...Theories: FreeSchema, SchemaDesc, SchemaDescType, CSetSchemaDescType,
SchemaDescTypeType, ob_num, codom_num, attr, attrtype
import ...Present: Presentation

# StructACSet Struct Generation
###############################
Expand Down Expand Up @@ -192,9 +191,7 @@ end
# StructACSet Operations
########################

""" This should really be in base, or at least somewhere other than this module...
"""
Base.Dict(nt::NamedTuple) = Dict(k => nt[k] for k in keys(nt))
Presentation(::StructACSet{S}) where S = Presentation(S)

# Accessors
###########
Expand Down Expand Up @@ -255,25 +252,19 @@ end
broadcast_findall(xs, array::AbstractArray) =
broadcast(x -> findall(y -> x == y, array), xs)

function get_attr_index(idx::Dict, k)
if k keys(idx)
idx[k]
else
[]
end
function get_attr_index(idx::AbstractDict, k)
get(idx, k, [])
end

function get_attr_index(idx::Dict, k::AbstractArray)
get_attr_index.(Ref(idx),k)
function get_attr_index(idx::AbstractDict, k::AbstractArray)
get_attr_index.(Ref(idx), k)
end

"""
We keep the main body of the code generating out of the @generated function
so that the code-generating function only needs to be compiled once.
"""
function incident_body(s::SchemaDesc,
idxed::Dict{Symbol,Bool}, unique_idxed::Dict{Symbol,Bool},
f::Symbol)
function incident_body(s::SchemaDesc, idxed::AbstractDict{Symbol,Bool},
unique_idxed::AbstractDict{Symbol,Bool}, f::Symbol)
if f s.homs
if idxed[f]
quote
Expand Down Expand Up @@ -308,17 +299,16 @@ end
@generated function _incident(acs::StructACSet{S,Ts,Idxed,UniqueIdxed},
part, ::Type{Val{f}}; copy::Bool=false) where
{S,Ts,Idxed,UniqueIdxed,f}
incident_body(SchemaDesc(S),Dict(Idxed),Dict(UniqueIdxed),f)
incident_body(SchemaDesc(S),pairs(Idxed),pairs(UniqueIdxed),f)
end

# Mutators
##########

@inline ACSetInterface.add_parts!(acs::StructACSet, ob::Symbol, n::Int) = _add_parts!(acs, Val{ob}, n)

function add_parts_body(s::SchemaDesc,
idxed::Dict, unique_idxed::Dict,
ob::Symbol)
function add_parts_body(s::SchemaDesc, idxed::AbstractDict,
unique_idxed::AbstractDict, ob::Symbol)
code = quote
m = acs.obs[$(ob_num(s, ob))]
nparts = m + n
Expand Down Expand Up @@ -363,16 +353,15 @@ end
@generated function _add_parts!(acs::StructACSet{S,Ts,Idxed,UniqueIdxed},
::Type{Val{ob}}, n::Int) where
{S, Ts, Idxed, UniqueIdxed, ob}
add_parts_body(SchemaDesc(S),Dict(Idxed),Dict(UniqueIdxed),ob)
add_parts_body(SchemaDesc(S),pairs(Idxed),pairs(UniqueIdxed),ob)
end

@inline ACSetInterface.set_subpart!(acs::StructACSet, part::Int, f::Symbol, subpart) =
_set_subpart!(acs, part, Val{f}, subpart)


function set_subpart_body(s::SchemaDesc,
idxed::Dict{Symbol,Bool}, unique_idxed::Dict{Symbol,Bool},
f::Symbol)
function set_subpart_body(s::SchemaDesc, idxed::AbstractDict{Symbol,Bool},
unique_idxed::AbstractDict{Symbol,Bool}, f::Symbol)
if f s.homs
if idxed[f]
quote
Expand Down Expand Up @@ -435,7 +424,7 @@ end
"""
@generated function _set_subpart!(acs::StructACSet{S,Ts,Idxed,UniqueIdxed},
part, ::Type{Val{f}}, subpart) where {S,Ts,Idxed,UniqueIdxed,f}
set_subpart_body(SchemaDesc(S),Dict(Idxed),Dict(UniqueIdxed),f)
set_subpart_body(SchemaDesc(S),pairs(Idxed),pairs(UniqueIdxed),f)
end

@inline Base.setindex!(acs::StructACSet, val, ob, part) = set_subpart!(acs, ob, part, val)
Expand Down Expand Up @@ -493,7 +482,7 @@ end

@generated function _rem_part!(acs::StructACSet{S,Ts,idxed}, ::Type{Val{ob}},
part::Int) where {S,Ts,ob,idxed}
rem_part_body(SchemaDesc(S),Dict(idxed),ob)
rem_part_body(SchemaDesc(S),pairs(idxed),ob)
end

function Base.copy(acs::StructACSet)
Expand Down
24 changes: 18 additions & 6 deletions src/categorical_algebra/CSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,25 @@ the codomain of the result is always of type `TypeSet`.
end
end

function FinDomFunctor(C::FinCats.FinCatPresentation, X::ACSet)
# TODO: Make struct `FinDomFunctorACSet` to avoid allocation.
ob_map = Dict(c => SetOb(X, nameof(c)) for c in ob_generators(C))
hom_map = Dict(f => SetFunction(X, nameof(f)) for f in hom_generators(C))
FinDomFunctor(ob_map, hom_map, C)
# Categories interop
####################

# FIXME: Object type should be `SetOb`, not `FinSet{Int}`.

""" Wrapper type to interpret attributed C-set as a functor.
"""
@auto_hash_equals struct ACSetFunctor{ACS<:ACSet} <:
Functor{FinCats.FinCatPresentation{Symbol,FreeSchema.Ob,FreeSchema.Hom},
TypeCat{FinSet{Int},FinDomFunction{Int}}}
acset::ACS
end
FinDomFunctor(pres::Presentation, X::ACSet) = FinDomFunctor(FinCat(pres), X)
FinDomFunctor(X::ACSet) = ACSetFunctor(X)

dom(F::ACSetFunctor) = FinCat(Presentation(F.acset))
codom(F::ACSetFunctor) = TypeCat{FinSet{Int},FinDomFunction{Int}}()

Categories.do_ob_map(F::ACSetFunctor, x) = SetOb(F.acset, x)
Categories.do_hom_map(F::ACSetFunctor, f) = SetFunction(F.acset, f)

# C-set transformations
#######################
Expand Down
35 changes: 31 additions & 4 deletions src/categorical_algebra/Categories.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ abstract type Functor{Dom<:Cat,Codom<:Cat} end
"""
@inline hom_map(F::Functor, f) = do_hom_map(F, f)

""" Identity functor on a category.
"""
@auto_hash_equals struct IdentityFunctor{Dom<:Cat} <: Functor{Dom,Dom}
dom::Dom
end
Expand All @@ -128,6 +130,30 @@ function Base.show(io::IO, F::IdentityFunctor)
print(io, ")")
end

""" Composite of functors.
"""
@auto_hash_equals struct CompositeFunctor{Dom,Codom,
F<:Functor{Dom,<:Cat},G<:Functor{<:Cat,Codom}} <: Functor{Dom,Codom}
fst::F
snd::G
end
Base.first(F::CompositeFunctor) = F.fst
Base.last(F::CompositeFunctor) = F.snd

dom(F::CompositeFunctor) = dom(first(F))
codom(F::CompositeFunctor) = codom(last(F))

do_ob_map(F::CompositeFunctor, x) = ob_map(F.snd, ob_map(F.fst, x))
do_hom_map(F::CompositeFunctor, f) = hom_map(F.snd, hom_map(F.fst, f))

function Base.show(io::IO, F::CompositeFunctor)
print(io, "compose(")
show(io, first(F))
print(io, ", ")
show(io, last(F))
print(io, ")")
end

show_type_constructor(io::IO, ::Type{<:Functor}) = print(io, "Functor")

function show_domains(io::IO, f; codomain::Bool=true, recurse::Bool=true)
Expand Down Expand Up @@ -196,8 +222,9 @@ const IdIdTransformation{C<:Cat} = IdentityTransformation{C,C,IdentityFunctor{C}
codom(F::Functor) = F.codom
id(C::Cat) = IdentityFunctor(C)

function compose(F::Functor, G::Functor)
codom(F) == dom(G) || error("Domain mismatch in composition $F$G")
function compose(F::Functor, G::Functor; strict::Bool=true)
!strict || codom(F) == dom(G) ||
error("Domain mismatch in composition $F$G")
compose_id(F, G)
end

Expand All @@ -224,7 +251,7 @@ const IdIdTransformation{C<:Cat} = IdentityTransformation{C,C,IdentityFunctor{C}
end

# XXX: Is this normalization of identities using multiple dispatch a good idea?
# In contrast to `Sets`, it requires a lot of boilerplate here.
# Unlike in `Sets`, it doesn't feel great since it requires so much boilerplate.

@inline compose_id(F::Functor, G::Functor) = do_compose(F, G)
@inline compose_id(F::Functor, ::IdentityFunctor) = F
Expand Down Expand Up @@ -253,7 +280,7 @@ end
id(compose_id(F, dom(β)))
@inline composeH_id(::IdentityFunctor, β::IdentityTransformation) = β

function do_compose end
do_compose(F::Functor, G::Functor) = CompositeFunctor(F, G)

@inline function do_composeH::Transformation, β::Transformation)
do_composeH(α, β, Val{:covariant})
Expand Down
15 changes: 4 additions & 11 deletions src/categorical_algebra/DataMigrations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ using ..Categories, ..FinCats, ..Limits, ..Diagrams, ..FinSets, ..CSets
using ...Graphs, ..FreeDiagrams
import ..Categories: ob_map, hom_map
using ..FinCats: make_map
import ...Present: Presentation

# Data types
############
Expand Down Expand Up @@ -142,11 +141,12 @@ end
#######################

function migrate(X::ACSet, F::ConjSchemaMigration)
X = FinDomFunctor(X)
tgt_schema = dom(F)
sets = make_map(ob_generators(tgt_schema)) do c
Fc = diagram(ob_map(F, c))
lim = limit(compose(Fc, X, strict=false))
J = dom(Fc)
lim = limit(Fc FinDomFunctor(codom(Fc), X))
names = Tuple(Symbol(ob_name(J, j)) for j in ob_generators(J))
TabularSet(NamedTuple{names}(Tuple(map(collect, legs(lim)))))
end
Expand All @@ -156,8 +156,7 @@ function migrate(X::ACSet, F::ConjSchemaMigration)
J′, Ff₀, Ff₁ = shape(codom(Ff)), shape_map(Ff), diagram_map(Ff)
names = keys(sets[d].table)
Ff₀ = NamedTuple{names}(Tuple(ob_map(Ff₀, j) for j in ob_generators(J′)))
Ff₁ = NamedTuple{names}(Tuple(SetFunction(X, nameof(component(Ff₁, j)))
# FIXME: Allow non-generator components.
Ff₁ = NamedTuple{names}(Tuple(hom_map(X, component(Ff₁, j))
for j in ob_generators(J′)))
FinFunction(row -> map((j,g) -> g(row[j]), Ff₀, Ff₁), sets[c], sets[d])
end
Expand Down Expand Up @@ -312,13 +311,7 @@ end
# Schema translation
####################

# FIXME: These functions do not belong here.

""" Get the Schema from an ACSet
"""
function Presentation(::StructACSet{S}) where S
return Presentation(S)
end
# FIXME: This function does not belong here.

""" FreeDiagram(pres::Presentation{FreeSchema, Symbol})
Expand Down
2 changes: 1 addition & 1 deletion src/categorical_algebra/FinCats.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ attribute types are regarded as the category's objects and the schema's
morphisms and attributes as the category's morphisms. Formalizing the schema as
a profunctor, this amounts to taking the collage of the profunctor.
"""
struct FinCatPresentation{T,Ob,Hom} <: FinCat{Ob,Hom}
@auto_hash_equals struct FinCatPresentation{T,Ob,Hom} <: FinCat{Ob,Hom}
presentation::Presentation{T}

function FinCatPresentation(pres::Presentation{T}) where T
Expand Down
10 changes: 5 additions & 5 deletions src/categorical_algebra/FinSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import ..FinCats: FinDomFunctor, ob_generators, hom_generators, is_discrete
import ..Limits: limit, colimit, universal, pushout_complement,
can_pushout_complement
import ..Subobjects: Subobject, SubobjectLattice
using ..Sets: SetFunctionCallable, SetFunctionIdentity
using ..Sets: IdentityFunction, SetFunctionCallable

# Finite sets
#############
Expand Down Expand Up @@ -165,7 +165,7 @@ const FinFunction{S, S′, Dom <: FinSet{S}, Codom <: FinSet{S′}} =
FinFunction(f::Function, dom, codom) =
SetFunctionCallable(f, FinSet(dom), FinSet(codom))
FinFunction(::typeof(identity), args...) =
SetFunctionIdentity((FinSet(arg) for arg in args)...)
IdentityFunction((FinSet(arg) for arg in args)...)
FinFunction(f::AbstractVector{Int}; kw...) =
FinDomFunctionVector(f, FinSet(isempty(f) ? 0 : maximum(f)); kw...)

Expand All @@ -190,7 +190,7 @@ const FinDomFunction{S, Dom<:FinSet{S}, Codom<:SetOb} = SetFunction{Dom,Codom}
FinDomFunction(f::Function, dom, codom) =
SetFunctionCallable(f, FinSet(dom), codom)
FinDomFunction(::typeof(identity), args...) =
SetFunctionIdentity((FinSet(arg) for arg in args)...)
IdentityFunction((FinSet(arg) for arg in args)...)

function FinDomFunction(f::AbstractVector, args...; index=false)
if index == false
Expand Down Expand Up @@ -306,13 +306,13 @@ force(f::IndexedFinDomFunction) = f
""" Whether the given function is indexed, i.e., supports efficient preimages.
"""
is_indexed(f::SetFunction) = false
is_indexed(f::SetFunctionIdentity) = true
is_indexed(f::IdentityFunction) = true
is_indexed(f::IndexedFinDomFunction) = true
is_indexed(f::FinDomFunctionVector{T,<:AbstractRange{T}}) where T = true

""" The preimage (inverse image) of the value y in the codomain.
"""
preimage(f::SetFunctionIdentity, y) = SVector(y)
preimage(f::IdentityFunction, y) = SVector(y)
preimage(f::FinDomFunction, y) = [ x for x in dom(f) if f(x) == y ]
preimage(f::IndexedFinDomFunction, y) = get_preimage_index(f.index, y)

Expand Down
Loading

0 comments on commit accf7a9

Please sign in to comment.