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

WIP - Permutation Groupoid #707

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 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
199 changes: 191 additions & 8 deletions src/categorical_algebra/FinSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export FinSet, FinFunction, FinDomFunction, TabularSet, TabularLimit,
force, is_indexed, preimage, VarFunction, LooseVarFunction,
JoinAlgorithm, SmartJoin, NestedLoopJoin, SortMergeJoin, HashJoin,
SubFinSet, SubOpBoolean, is_monic, is_epic
SubFinSet, SubOpBoolean, is_monic, is_epic, FinBijection

using StructEquality
using DataStructures: OrderedDict, IntDisjointSets, union!, find_root!
Expand All @@ -25,7 +25,8 @@
using ..FinCats: dicttype
import ..Limits: limit, colimit, universal, BipartiteColimit
import ..Subobjects: Subobject
using ..Sets: IdentityFunction, SetFunctionCallable
using ..Sets: IdentityFunction, SetFunctionCallable, AbsBijectionWrap,
BijectionBimap, BijectionThinWrap, unwrap

# Finite sets
#############
Expand Down Expand Up @@ -54,7 +55,8 @@

Base.iterate(set::FinSetInt, args...) = iterate(1:set.n, args...)
Base.length(set::FinSetInt) = set.n
Base.in(set::FinSetInt, elem) = in(elem, 1:set.n)
# Code review note: the arguments seem to have been placed backwards originally
Base.in(elem, set::FinSetInt) = in(elem, 1:set.n)

Base.show(io::IO, set::FinSetInt) = print(io, "FinSet($(set.n))")

Expand Down Expand Up @@ -285,12 +287,27 @@

""" Function in **FinSet** represented explicitly by a vector.
"""
const FinFunctionVector{S,T,V<:AbstractVector{T}} =
FinDomFunctionVector{T,V,<:FinSet{S,T}}

Base.show(io::IO, f::FinFunctionVector) =
# Code review note: This type declaration is regarded by Julia
# as equal (i.e. ==) to the extant declaration. I feel that giving
# `FinFunctionVector` an explicit parameter to represent the type of the
# codomain improves the self-documentation aspect of the code.
const FinFunctionVector{S,T,V<:AbstractVector{T},Codom<:FinSet{S,T}} =
FinDomFunctionVector{T,V,Codom}

# Code review note: showing just the length of the codomain is not ideal when
# the codomain is not a `FinSetInt`
Base.show(io::IO, f::FinFunctionVector{Int}) =
print(io, "FinFunction($(f.func), $(length(dom(f))), $(length(codom(f))))")

function Base.show(io::IO, f::F) where {F<:FinFunctionVector}
Sets.show_type_constructor(io, F)
print(io, "(")
show(io, f.func)
print(io, ", $(length(dom(f))), ")
Sets.show_domains(io, f, domain=false)
print(io, ")")
end

Sets.do_compose(f::FinFunctionVector, g::FinDomFunctionVector) =
FinDomFunctionVector(g.func[f.func], codom(g))

Expand Down Expand Up @@ -443,12 +460,16 @@
is_indexed(f::IdentityFunction) = true
is_indexed(f::IndexedFinDomFunctionVector) = true
is_indexed(f::FinDomFunctionVector{T,<:AbstractRange{T}}) where T = true
is_indexed(f::BijectionThinWrap) = is_indexed(f.func)
is_indexed(f::BijectionBimap) = true

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

Check warning on line 471 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L471

Added line #L471 was not covered by tests
preimage(f::BijectionBimap, y) = f.inv(y)

@inline get_preimage_index(index::AbstractDict, y) = get(index, y, 1:0)
@inline get_preimage_index(index::AbstractVector, y) = index[y]
Expand Down Expand Up @@ -542,8 +563,10 @@

""" Function in **FinSet** represented by a dictionary.
"""
const FinFunctionDict{K,D<:AbstractDict{K},Codom<:FinSet} =
const FinFunctionDict{K,D<:AbstractDict{K},S,Codom<:FinSet{S}} =
FinDomFunctionDict{K,D,Codom}
# Code review note: The additional parameter `S` allows `FinFunctionDict` to be
# recognized as a subtype of `FinFunction`

FinFunctionDict(d::AbstractDict, codom::FinSet) = FinDomFunctionDict(d, codom)
FinFunctionDict(d::AbstractDict{K,V}) where {K,V} =
Expand All @@ -553,6 +576,166 @@
FinDomFunctionDict(dicttype(D)(x => g.func[y] for (x,y) in pairs(f.func)),
codom(g))

# Category of finite sets and bijections
########################################

""" Bijection between finite sets.
"""
const FinBijection{S, S′, Dom <: FinSet{S}, Codom <: FinSet{S′}} =
Bijection{Dom, Codom}

FinBijection(f, args...) = FinBijection(f, (FinSet(a) for a in args)...)
FinBijection(f::Function, dom::FinSet, codom::FinSet) =

Check warning on line 588 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L587-L588

Added lines #L587 - L588 were not covered by tests
BijectionThinWrap(SetFunction(f, dom, codom))
FinBijection(f::FinFunction) = BijectionThinWrap(f)

Check warning on line 590 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L590

Added line #L590 was not covered by tests
FinBijection(f::FinFunction, g::FinFunction) = BijectionBimap(unwrap(f), unwrap(g))
FinBijection(f::AbstractVector) =
BijectionThinWrap(FinDomFunction(f, FinSet(Set(f))))
FinBijection(f::AbstractVector, a, args...) =

Check warning on line 594 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L594

Added line #L594 was not covered by tests
BijectionThinWrap(FinDomFunction(f, FinSet(a), args...))
FinBijection(f::AbstractDict, args...) =
BijectionThinWrap(FinFunction(f, args...))

function FinBijection(f::Union{AbstractDict{K,Int},AbstractVector{Int}}) where K
function minandmax(p::Tuple{<:Number, <:Number}, n::Int)::Tuple{Int,Int}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little overzealous for a type assert, the julia compiler will infer this method correctly.

(Int(min(p[1], n)), Int(max(p[2], n)))
end
minval, maxval = reduce(minandmax, values(f), init=(Inf, -Inf))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use typemax(Int), typemin(Int) for this sentinel value, since Inf and -Inf are Float64. You probably also want to return the min, then the max so that you can interpret it as a range. min:max

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will change the sentinel value. About the ordering thing, if I'm not misunderstanding you, minandmax is already returning the values in that order, as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nevermind that. reading min and max too many times in a row, I forgot which is which.

len = length(f)
cod = minval == 1 && maxval == len ? FinSet(len) : Set(v)
BijectionThinWrap(FinFunction(f, cod))
end

Sets.show_type_constructor(io::IO, ::Type{<:FinBijection}) =

Check warning on line 609 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L609

Added line #L609 was not covered by tests
print(io, "FinBijection")

""" Abstract (alias) type for bijections on finite sets which are implemented
by wrapping another `FinFunction` object.
"""
const FinBijectionWrap{S,S′,Dom<:FinSet{S},Codom<:FinSet{S′},
F<:FinFunction{S,S′,Dom,Codom}} = AbsBijectionWrap{Dom, Codom, F}

force(f::FinBijectionWrap) = FinBijection(force(unwrap(f)))

Check warning on line 618 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L618

Added line #L618 was not covered by tests

""" Alias for all `FinBijection`s that wrap `Vector`s.
"""
const FinBijectionVector = Union{
AbsBijectionWrap{
FinSetInt, Codom, FinFunctionVector{S,T,V,Codom}
} where {S, T, V<:AbstractVector{T}, Codom<:FinSet{S,T}},
AbsBijectionWrap{
FinSetInt, FinSetInt, IndexedFinFunctionVector{V,Index}
} where {V<:AbstractVector{Int}, Index},
}

force(f::FinBijectionVector) = f

Check warning on line 631 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L631

Added line #L631 was not covered by tests

Sets.do_compose(f::FinBijectionVector, g::FinBijectionVector) =
BijectionThinWrap(Sets.do_compose(unwrap(f), unwrap(g)))

""" Alias for all `FinBijection`s that wrap `Dict`s.
"""
const FinBijectionDict{K,D<:AbstractDict{K},S,Codom<:FinSet{S}} =
AbsBijectionWrap{
FinSetCollection{Base.KeySet{K,D}}, Codom, FinFunctionDict{K,D,S,Codom}
} where {K, D<:AbstractDict{K}, S, Codom<:FinSet{S}}

force(f::FinBijectionDict) = f

Check warning on line 643 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L643

Added line #L643 was not covered by tests

Sets.do_compose(f::FinBijectionDict, g::FinBijectionDict) =

Check warning on line 645 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L645

Added line #L645 was not covered by tests
BijectionThinWrap(Sets.do_compose(unwrap(f), unwrap(g)))

function Sets.do_inv(f::FinBijection{S,S′,Dom,Codom}) where
{S,S′,T,T′,Dom<:FinSet{S,T},Codom<:FinSet{S′,T′}}
domain, cod = dom(f), codom(f)
func = S′ == Int ? Vector{T}(undef, length(cod)) : Dict{T′,T}()
for x in domain
func[f(x)] = x
end
FinDomFunction(func, domain)
end

""" Finite bijection whose form in cycle notation is known.

Computing a bijection's cycles from its function requires the same computations
as computing the inverse. Furthermore, the inverse of a function can be computed
from its cycles just as easily as the function itself. This type is designed
with these facts in mind.
"""
struct FinBijectionCycles{S,T,Dom<:FinSet{S,T},F<:FinFunction{S,S,Dom,Dom},
G<:FinFunction{S,S,Dom,Dom}} <: AbsBijectionWrap{Dom,Dom,F}
func::BijectionBimap{Dom,Dom,F,G}
cycles::Vector{Vector{T}}
end

function FinBijectionCycles(f::FinBijection{S,S,Dom,Dom}) where

Check warning on line 671 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L671

Added line #L671 was not covered by tests
{S,T,Dom<:FinSet{S,T}}
domain = dom(f)
cycles = Vector{T}[]
if S == Int
invfunc, used = Vector{Int}(undef, length(domain)), falses(length(domain))

Check warning on line 676 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L673-L676

Added lines #L673 - L676 were not covered by tests
else
invfunc, used = Dict{T,T}(), Dict(k=>false for k in domain)

Check warning on line 678 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L678

Added line #L678 was not covered by tests
end
# Developer's note: this loop recapitulates the approach of the extant
# function `Permutations.cycles`, with the difference that it ignores
# trivial cycles and interleaves the computation of the inverse
for i in domain
if used[i]; continue end
used[i] = true
j = f(i)
invfunc[j] = i
if j != i
cycle = [i]
while true
push!(cycle, j)
used[j] = true
k = j
j = f(j)
invfunc[j] = k
if j == i; break end
end
push!(cycles, cycle)

Check warning on line 698 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L683-L698

Added lines #L683 - L698 were not covered by tests
end
end
bimap = BijectionBimap(f, FinDomFunction(invfunc, dom(f)))
FinBijectionCycles(bimap, cycles)

Check warning on line 702 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L700-L702

Added lines #L700 - L702 were not covered by tests
end

function FinBijectionCycles(f::AbstractVector{<:AbstractVector{T}},

Check warning on line 705 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L705

Added line #L705 was not covered by tests
dom::FinSet{S,T}) where {S,T}
domlen = length(dom)
if S == Int
func, invfunc = Vector{Int}(undef, domlen), Vector{Int}(undef, domlen)

Check warning on line 709 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L707-L709

Added lines #L707 - L709 were not covered by tests
else
func, invfunc = Dict{T,T}(), Dict{T,T}()

Check warning on line 711 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L711

Added line #L711 was not covered by tests
end
lengths = map(length, f)
for (i, cycle) in enumerate(f)
len = lengths[i]
lenmod = n -> mod(n, len)
for (j, x) in enumerate(cycle)
func[x], invfunc[x] = cycle[lenmod(j+1)], cycle[lenmod(j-1)]
end
end
bimap = FinBijection(FinDomFunction(func, dom), FinDomFunction(invfunc, dom))
FinBijectionCycles(bimap, f)

Check warning on line 722 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L713-L722

Added lines #L713 - L722 were not covered by tests
end

Base.:(==)(f::FinBijectionCycles, g::FinBijectionCycles) = f.func == g.func
Base.hash(f::FinBijectionCycles) = hash(f.func)

Check warning on line 726 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L725-L726

Added lines #L725 - L726 were not covered by tests

Sets.compose_inv(f::FinBijectionCycles, g::SetFunction) = compose_inv(f.func, g)
Sets.compose_inv(f::SetFunction, g::FinBijectionCycles) = compose_inv(f, g.func)
Sets.compose_inv(f::FinBijectionCycles, g::FinBijectionCycles) =

Check warning on line 730 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L728-L730

Added lines #L728 - L730 were not covered by tests
compose_inv(f.func, g.func)

Base.inv(f::FinBijectionCycles) =

Check warning on line 733 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L733

Added line #L733 was not covered by tests
FinBijectionCycles(inv(f.func), [reverse(c) for c in f.cycles])

is_indexed(f::FinBijectionCycles) = true
preimage(f::FinBijectionCycles, y) = preimage(f.func, y)

Check warning on line 737 in src/categorical_algebra/FinSets.jl

View check run for this annotation

Codecov / codecov/patch

src/categorical_algebra/FinSets.jl#L736-L737

Added lines #L736 - L737 were not covered by tests

# Limits
########

Expand Down
Loading