diff --git a/docs/literate/dynam.jl b/docs/literate/dynam.jl new file mode 100644 index 000000000..f786edcf8 --- /dev/null +++ b/docs/literate/dynam.jl @@ -0,0 +1,172 @@ +# # Decorated Cospans of Dynamical Systems + +using Catlab +import Catlab.Theories: compose, id, dom, codom +using Catlab.CategoricalAlgebra +import Catlab.CategoricalAlgebra.Limits: CompositePushout +using Catlab.Decorated +using Catlab.Sheaves +import Catlab.CategoricalAlgebra.Categories: do_ob_map, do_hom_map, LargeCatSize + +# ## Defining our Functor +# +# Decorated Cospans requires that you map objects of FinSet (Natural Numbers) to +# Sets of systems with that size. In this case our systems of size $n$ are vector fields on $\mathbb{R}^n$. +# The systems need to be able to act on a vector in their state space to produce a tangent vector. +struct DynamElt + n::Int + v::Function # ℝⁿ→Tℝⁿ +end + +(v::DynamElt)(x::AbstractVector) = v.v(x) + +# A DynamMap is a wrapper around a FinFunction to hang our functor's action on. + +struct DynamMap + f::FinFunction +end + +dom(f::DynamMap) = dom(f.f) +codom(f::DynamMap) = codom(f.f) + +# A `DynamMap` acts of a `DynamElt` $v$ by computing a new `DynamElt` $u$ +# that is derived by a change of variables. +# You first pullback the state of $u$ to a state of $v$ by precomposition by $f$. +# Then you apply the vector field $v$ by calling the function to get a tangent vector $v(f^*(x))$. +# Then you pushforward the tangent vector by adding up variables that get mapped to the same state variable by $f$. + +(f::DynamMap)(v::DynamElt) = begin + FinSet(v.n) == dom(f) || error("Domain of finfunction must match state space of dynamical system") + pushforward = do_hom_map(FVectPushforward, f.f) + pullback = do_hom_map(FVectPullback(), f.f) + DynamElt(dom(f).n, pushforward∘v.v∘pullback) +end + +# Our category of dynamical systems is a subcategory of Set. +# Each `Int` represents the set of vector fields on $n$ variables and +# each `DynamMap` represents a function from vector fields on $n$ variables to vector fields on $m$ variables. + +struct DynamCat <: Category{Int, DynamMap, LargeCatSize} end + +F = Functor(identity, f -> DynamMap(f), FinSetCat(), DynamCat()) + +# We can test out our dynamics functor on some basic equations. +# These are using the identity function x->x as the starting vector field. +# This allows us to write tests that are easy to reason about. + +using Test +f = FinFunction([2,1], 2) +ϕ = do_hom_map(F, f) +@test dom(ϕ) == FinSet(2) + +v = x->[x[1],x[2]] +u = ϕ(DynamElt(2, v)) +@test u([π,exp(1)]) == [π, exp(1)] + +g = FinFunction([2,1,2], 2) +ψ = do_hom_map(F, g) +w = identity +u = ψ(DynamElt(3, w)) +@test u([π,exp(1)]) == [π, 2exp(1)] + +f = FinFunction([2,1,2,1], 2) +ϕ = do_hom_map(F, f) +v = identity +u = ϕ(DynamElt(4, v)) +@test u([π,exp(1)]) == [2π, 2exp(1)] + +f = FinFunction([2,1,2,1], 3) +ϕ = do_hom_map(F, f) +v = identity +u = ϕ(DynamElt(4, v)) +@test u([π,exp(1),1]) == [2π, 2exp(1), 0] + +# We can use a polynomial vector field for some interesting computations. +# Notice that you can quickly build some complex vector fields with the action of +# FinFunctions on polynomial primitives. + +f = FinFunction([2,1,2,1], 3) +ϕ = do_hom_map(F, f) +v = x->[x[1]*x[2], x[2], x[3]*x[1], x[4]] +u = ϕ(DynamElt(4, v)) +@test u([π,exp(1),1]) == [2π, exp(1)*π + exp(1)^2, 0] + +# Now let's do some decorated cospans in Dynam. We start by defining our laxator $L$. + +# ## Defining the Laxator +# +# The Laxator is usually easier than the functor. +# In this case we just have to implement stacking vector fields on top of each other. +# This is the categorical product of functions in set. +# If you have functions $f\colon \mathbb{R}^n\to T\mathbb{R}^n$ and $f\colon \mathbb{R}^m\to T\mathbb{R}^m$, +# the categorical product is a function $f\times g \colon \mathbb{R}^{n+m}\to T\mathbb{R}^{n+m}$. +# You just call $f$ on the first $n$ variables and $g$ on the $n+1:n+m$ variables and concatenate their output. +# In Vect (the cateogry of vector spaces and linear maps), this categorical product is also the coproduct, and we call it the direct sum ($\oitmes$). + +L(vs::Vector{DynamElt}) = begin + @show vs + indices = cumsum(v.n for v in vs) + pushfirst!(indices, 0) + @show indices + f(x) = begin + dx = map(enumerate((vs))) do (i,v) + xᵢ = x[indices[i]+1:indices[i+1]] + dxᵢ = v(xᵢ) + end + reduce(vcat, dx) + end + return DynamElt(indices[end], f) +end + + +# And we can test that our Laxator is working + +w = L([DynamElt(2, identity),DynamElt(3, identity)]) +@test w(1:5) == 1:5 + +w = L([DynamElt(2, x->[x[1]*x[2],x[2]]),DynamElt(3, x->[x[1]*x[2], x[2], x[3]])]) +@test w(1:5) == [2*1, 2, 3*4, 4,5] + +# ## Decorated Cospans of Dynam +# Now that our Functor and Laxator are working, we can move on to the Decorated Cospans. +# Note that we call the lax monoidal functor the "decora*tor*" and the +# systems that we use to decorate our cospans the "decora*tions*". +# The decorations are elements in the image of the decorator. + +# Here we are gluing the second and third variables of $v$ to the first and second variables of $u$. + +D = Decorated.Decorator(F, L) +f = FinFunction([2,3], 3) +g = FinFunction([1,2], 3) +v = DynamElt(3, identity) +u = DynamElt(3, identity) +w = glue(D, pushout(f,g), [v,u]) +@test w([1,2,3,5]) == [1,4,6,5] + +# With interesting vector fields we can get cool dynamics. + +v = DynamElt(3, x->[x[1]*x[2], x[2], x[3]]) +u = DynamElt(3, x->[x[1], x[2]*x[3], x[3]]) +w = glue(D, pushout(f,g), [v,u]) +@test w([1,2,3,4]) == [1*2,2,3,0] + [0,2,3*4,4] +@test w([2,3,5,7]) == [2*3,3,5,0] + [0,3,5*7,7] + +# ## Euler's Method + +# Can apply Euler's method to produce trajectories of these systems. + +v = DynamElt(3, x->[x[1]*x[2], x[2], -x[3]]) +u = DynamElt(3, x->[x[1], x[2]*x[3]/x[1], x[3]]) +w = glue(D, pushout(f,g), [v,u]) +x = Vector{Float64}[[2,3,5,7]] +Δt = 0.05 +map(1:10) do i + xᵢ₊₁ = x[i] + w(x[i])*Δt + push!(x, xᵢ₊₁) +end +traj = hcat(x...)'; + +# We can look at our trajectory as a table. + +using PrettyTables +pretty_table(traj) \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index adc69beeb..16d77308f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -75,6 +75,9 @@ makedocs( "generated/graphics/tikz_wiring_diagrams.md", "generated/graphics/layouts_vs_drawings.md", ], + "Decorated Cospans" => Any[ + "generated/dynam.md", + ], ], "Modules" => Any[ "apis/gats.md", @@ -85,6 +88,7 @@ makedocs( "apis/graphics.md", "apis/programs.md", "apis/sheaves.md", + "apis/decorated.md", ], "Developer Docs" => Any[ "devdocs/style.md", diff --git a/docs/src/apis/decorated.md b/docs/src/apis/decorated.md new file mode 100644 index 000000000..1cae2916e --- /dev/null +++ b/docs/src/apis/decorated.md @@ -0,0 +1,9 @@ +# [Decorated](@id decorated) + +The `Decorated` module contains functionality for implementing Decorated Cospans and Decorated Corelations. + +```@autodocs +Modules = [ + Decorated + ] +``` diff --git a/src/Catlab.jl b/src/Catlab.jl index 51d69d785..c2f81c1bc 100644 --- a/src/Catlab.jl +++ b/src/Catlab.jl @@ -11,6 +11,7 @@ include("wiring_diagrams/WiringDiagrams.jl") include("graphics/Graphics.jl") include("programs/Programs.jl") include("sheaves/Sheaves.jl") +include("decorated/Decorated.jl") @reexport using .GATs @reexport using .Theories @@ -20,5 +21,6 @@ include("sheaves/Sheaves.jl") @reexport using .Graphics @reexport using .Programs @reexport using .Sheaves +@reexport using .Decorated end diff --git a/src/decorated/Decorated.jl b/src/decorated/Decorated.jl new file mode 100644 index 000000000..eb91a2bc2 --- /dev/null +++ b/src/decorated/Decorated.jl @@ -0,0 +1,202 @@ +module Decorated +using Catlab +using Catlab.Theories +import Catlab.Theories: compose +using Catlab.CategoricalAlgebra +import Catlab.CategoricalAlgebra: legs, apex +using Catlab.CategoricalAlgebra.Categories +import Catlab.CategoricalAlgebra.Categories: do_ob_map, do_hom_map, LargeCatSize +import Catlab.CategoricalAlgebra.Limits: CompositePushout +using Catlab.Sheaves + +export AbstractDecorator, functor, laxator, DecoratedCospan, VectCospan, + CorelationDecorator, FVectPushforward, Vect, SummationProjection, + glue + +""" AbstractDecorator + +An abstract class to overload for define a set of decorations. +""" +abstract type AbstractDecorator end + +""" functor(d::AbstractDecorator) + +Get the functor part of the decorator +""" +functor(d::AbstractDecorator) = error("Not Implemented: functor for $(typeof(d))") + +""" laxator(d::AbstractDecorator) + +Get the laxator part of the decorator +""" +laxator(d::AbstractDecorator) = error("Not Implemented: laxator for $(typeof(d))") + +""" Decorator + +A struct for a decorator functor and a laxator. +""" +struct Decorator{F,L} <: AbstractDecorator + functor::F + laxator::L +end + +functor(d::Decorator) = d.functor +laxator(d::Decorator) = d.laxator + + +""" DecoratedCospan + +An abstract class decorated cospans. You need to implement: + +1. legs +2. apex +3. decorator +4. decoration +""" +abstract type DecoratedCospan end + +decorator(f::DecoratedCospan) = error("Not Implemented: decorator for $(typeof(f))") +legs(f::DecoratedCospan) = error("Not Implemented: legs for $(typeof(f))") +apex(f::DecoratedCospan) = error("Not Implemented: apex for $(typeof(f))") + +coequalizer_map(d::CompositePushout) = d.coeq.cocone.legs[1] + +""" glue(F::Decorator, cospan::CompositePushout, decorations::AbstractVector) + +Compute the composite system by the decorated cospan construction. +""" +function glue(F::Decorator, cospan::CompositePushout, decorations::AbstractVector) + πq = coequalizer_map(cospan) + ϕ = do_hom_map(F.functor, πq) + ϕ(F.laxator(decorations)) +end + +function glue(D::Decorator, f::DecoratedCospan, g::DecoratedCospan) + # this notation is from Fong 2017 https://arxiv.org/abs/1703.09888 page 13. + # invoke the decorated cospans part to build the composite + oy = legs(f)[2] + iy = legs(g)[1] + p = pushout(oy,iy) + decorations = [f.decoration, g.decoration] + composite = glue(D.action, p, decorations) + + # put the new legs on for the interface. + ix = legs(f)[1] + oz = legs(g)[2] + return typeof(f)(composite, Cospan(ix, oz)) +end + +""" FactorizationSystem{C} + +An abstract type for representing an orthogonal factorization. Examples include: + + 1. `EpiMono` +""" +abstract type FactorizationSystem{C<:Category} end + +# API for a generic factorization system. +category(::FactorizationSystem{C}) where C = C +epis(fs::FactorizationSystem) = error("Not Implemented: epis for $(typeof(fs))") +monos(fs::FactorizationSystem) = error("Not Implemented: monos for $(typeof(fs))") +factor(em::FactorizationSystem, f) = error("Not Implemented: factor($(typeof(em)),$(typeof(f)))") + + +struct FinSetEpi <: Category{FinSet, FinFunction, LargeCatSize} end +struct FinSetMono <: Category{FinSet, FinFunction, LargeCatSize} end + +""" EpiMono + +The factorization system for the classic epimorphisms (surjective functions) and monomorphisms (injective functions) in Set. +""" +struct EpiMono <: FactorizationSystem{FinSetCat} end + +epis(::EpiMono) = FinSetEpi() +monos(::EpiMono) = FinSetMono() +factor(::EpiMono, f::FinFunction) = epi_mono(f) + +""" CorelationDecorator + +A struct for what you need for a decorated corelations construction. The fields are: + + 1. action: the covariant functor from C to Set (and its laxator) + 2. coaction: the contravariant functor from C^op to Set + 3. factorization: a factorization system on C + +""" +struct CorelationDecorator{F,L,R} <: AbstractDecorator + action::Decorator{F,L} + coaction::R + factorization::FactorizationSystem +end + +""" glue(D::CorelationDecorator, f::DecoratedCospan, g::DecoratedCospan) + +Compute the composite system by the decorated cospan construction. +""" +function glue(D::CorelationDecorator, f::DecoratedCospan, g::DecoratedCospan) + # this notation is from Fong 2017 https://arxiv.org/abs/1703.09888 page 13. + # invoke the decorated cospans part to build the composite + oy = legs(f)[2] + iy = legs(g)[1] + p = pushout(oy,iy) + decorations = [f.decoration, g.decoration] + composite = glue(D.action, p, decorations) + + # project onto the image of the outer ports + ix = legs(f)[1] + oz = legs(g)[2] + + jN = legs(p)[1] + jM = legs(p)[2] + + coprod2composite = copair(compose(ix, jN), compose(oz, jM)) + _, m = factor(D.factorization, coprod2composite) + decorator = do_hom_map(D.coaction,m)(composite) + return decorator +end + +""" FreeVect{T} where T <: Number + +The covariant free vector space functor. +The returned function f✶ sums over preimages. +""" +FVectPushforward = Functor(identity, # identity on objects + # specify the pushforward construction + f->(v->begin + z = zero(eltype(v)) + map(codom(f)) do i + sum(v[j] for j in preimage(f,i); init=z) + end end), + # covariant functor from FinSetCat to FinVect + FinSetCat(), FinVect() +) + +Vect = Decorator(FVectPushforward, vs -> reduce(vcat, vs)) +SummationProjection = CorelationDecorator(Vect, Sheaves.FVectPullback(), EpiMono()) + +""" VectCospan + +A type to hold cospans decorated by the SummationProjection decorator. +""" +struct VectCospan <: DecoratedCospan + decoration::AbstractVector + cospan +end + +decorator(::VectCospan) = SummationProjection +legs(f::VectCospan) = legs(f.cospan) +apex(f::VectCospan) = apex(f.cospan) + +function Base.show(io::IO, f::VectCospan) + println(io, "DecoratedCospan begin") + print(io, "cospan decorator: Free Vector Space Functor") + print(io, "corelations decorator: SummationProjection") + println(io, "decoration: $(f.decoration)") + println(io, "legs: ") + map(legs(f.cospan)) do l + println(io, " $l") + end + print(io, "end") +end + +end \ No newline at end of file diff --git a/src/sheaves/FVect.jl b/src/sheaves/FVect.jl index 1d4784d68..8d571e0e2 100644 --- a/src/sheaves/FVect.jl +++ b/src/sheaves/FVect.jl @@ -12,19 +12,6 @@ do_ob_map(::FVectPullback, n::FinSet) = n do_hom_map(::FVectPullback, f::FinFunction) = (v->v[f.(dom(f))]) # as a callable functor this would be: FVectPullback = Functor(identity, f->(v->v[f.(dom(f))]), OppositeCat(FinSetCat()), FinVect()) -""" FreeVect{T} where T <: Number - -The covariant free vector space functor. -The returned function f✶ sums over preimages. -""" -FVectPushforward = Functor(identity, # identity on objects - # specify the pushforward construction - f->(v->map(dom(f)) do i - sum(v[j] for j in preimage(f,i)) - end), - # covariant functor from FinSetCat to FinVect - FinSetCat(), FinVect() -) const FinMat = TypeCat(MatrixDom, Matrix) const FinMatT = typeof(FinMat) diff --git a/test/decorated/Decorated.jl b/test/decorated/Decorated.jl new file mode 100644 index 000000000..c512043b1 --- /dev/null +++ b/test/decorated/Decorated.jl @@ -0,0 +1,38 @@ +using Test +using Catlab +using Catlab.CategoricalAlgebra +import Catlab.CategoricalAlgebra.Limits: CompositePushout +using Catlab.Decorated + +f = FinFunction([1,2], 3) +g = FinFunction([1,2], 3) + +h = FinFunction([1,2], 3) +p = pushout(f,g); +p3 = pushout([f,g,h]); + +@testset "Structure" begin + @test codom(p.coeq.cocone.legs[1]) == apex(p) + @test collect(p.coeq.cocone.legs[1]) == [1,2,3,1,2,4] + @test reduce(vcat, [[1,2,3],[1,2,4]]) == [1,2,3,1,2,4] + @test typeof(p) <: CompositePushout +end + +@testset "Gluing" begin + @test glue(Vect, p, [[1,2,3],[1,2,4]]) == [2,4,3,4] + @test glue(Vect, p3, [[1,2,3],[1,2,4],[1,2,5]]) == [3,6,3,4,5] +end + +@testset "DecoratedCorelations" begin + f₁ = VectCospan([1,2,3], Cospan(FinFunction([1], 3), FinFunction([1,2],3))) + f₂ = VectCospan([1,2,4], Cospan(FinFunction([1,2], 3), FinFunction([2],3))) + # @show f₁ + @test glue(SummationProjection, f₁,f₂) == [2,4] + f₁ = VectCospan([1,2,3], Cospan(FinFunction([1,2], 3), FinFunction([1,2],3))) + f₂ = VectCospan([1,2,4], Cospan(FinFunction([1,2], 3), FinFunction([2,3],3))) + @test glue(SummationProjection, f₁,f₂) == [2,4,4] + # Don't know how to do multi-arity corelations yet. + # glue(SummationProjection, p3, [[1,2,3],[1,2,4],[1,2,5]]) == [3,6,3,4,5] + # p4 = pushout(FinFunction([1,2], 4), FinFunction([1,2],4)) + # glue(SummationProjection, p4, [[1,2,3,4],[1,2,3,4]])# == [2,4] +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index a4a34e6aa..eeb1dda48 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,3 +31,7 @@ end @testset "Sheaves" begin include("sheaves/sheaves.jl") end + +@testset "Decorated" begin + include("decorated/Decorated.jl") +end \ No newline at end of file