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

Composite functions and functors #544

Merged
merged 6 commits into from
Nov 2, 2021
Merged
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
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