AlgebraicJulia / Structured-Epidemic-Modeling

Results and software for our paper on structured epidemic modeling
BSD 2-Clause "Simplified" License
12 stars 5 forks source link

Issues trying to get simple composition working #6

Closed sdwfrost closed 1 year ago

sdwfrost commented 1 year ago

I'm trying to get the worlds simplest composition working (basically SI \cdot IR -> SIR), but oapply doesn't seem to work here (using composition.ipynb as a template). I like to add suffixes to my variable names so I know their types.


using AlgebraicPetri,AlgebraicPetri.TypedPetri
using Catlab, Catlab.CategoricalAlgebra, Catlab.Programs
using Catlab.WiringDiagrams
using AlgebraicDynamics.UWDDynam

epi_transitions = LabelledPetriNet(
  [:Pop],
  :infection=>((:Pop, :Pop)=>(:Pop, :Pop)),
  :recovery=>(:Pop=>:Pop),
  :strata=>(:Pop=>:Pop)
)

si_uwd = @relation () where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end
si_acs = oapply_typed(epi_transitions, si_uwd, [:β])
si_lpn = dom(si_acs)

ir_uwd = @relation () where (I::Pop, R::Pop) begin
    recovery(I, R)
end
ir_acs = oapply_typed(epi_transitions, ir_uwd, [:γ])
ir_lpn = dom(ir_acs)

sir_uwd = @relation () where (S::Pop, I::Pop, R::Pop) begin
    si(S, I)
    ir(I, R)
end

oapply(sir_uwd, Dict(
    :si => si_lpn,
    :ir => ir_lpn,
))

The error:

ERROR: MethodError: no method matching oapply(::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, ::Vector{LabelledPetriNet}, ::Nothing)

Closest candidates are:
  oapply(::AbstractUWD, ::AbstractDict, ::Union{Nothing, AbstractDict}; hom_attr, ob_attr, kwargs...)
   @ Catlab ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:21
  oapply(::AbstractUWD, ::AbstractVector{<:StructuredMulticospan{L}}, ::Union{Nothing, AbstractVector}; return_colimit) where L
   @ Catlab ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:77
  oapply(::AbstractUWD, ::AbstractDict{S, R}) where {S, R<:AbstractResourceSharer}
   @ AlgebraicDynamics ~/.julia/packages/AlgebraicDynamics/s3uNa/src/uwd_dynam.jl:298
  ...

Stacktrace:
 [1] oapply(composite::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, hom_map::Dict{Symbol, LabelledPetriNet}, ob_map::Nothing; hom_attr::Symbol, ob_attr::Symbol, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Catlab.WiringDiagrams.WiringDiagramAlgebras ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:28
 [2] oapply(composite::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, hom_map::Dict{Symbol, LabelledPetriNet}, ob_map::Nothing)
   @ Catlab.WiringDiagrams.WiringDiagramAlgebras ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:21
 [3] oapply(composite::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, hom_map::Dict{Symbol, LabelledPetriNet})
   @ Catlab.WiringDiagrams.WiringDiagramAlgebras ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:21
 [4] top-level scope
   @ REPL[112]:1

Any pointers on how I'm going wrong?

jpfairbanks commented 1 year ago

I think your problem is that si_lpn and ir_lpn are LabelledPetriNets and not MultiCospans thereof. They don't have the interface data saying which variables need to be exposed. I think when you create the typed PNs

si_uwd = @relation () where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end

you need to add the interface variables in the signature in the @relation macro

si_uwd = @relation (S::Pop,I::Pop) where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end

But when every variable is exposed, I think there where clause is optional:

si_uwd = @relation (S::Pop,I::Pop) begin
    infection(S, I, I, I)
end
sdwfrost commented 1 year ago

I tried that, but I got an error:

si_uwd = @relation (S::Pop,I::Pop) where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end
ERROR: Expected name as positional or keyword argument
Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:35
  [2] #15
    @ ~/.julia/packages/Catlab/NhhmR/src/programs/RelationalPrograms.jl:244 [inlined]
  [3] iterate
    @ ./generator.jl:47 [inlined]
  [4] _collect(c::SubArray{Any, 1, Vector{Any}, Tuple{UnitRange{Int64}}, true}, itr::Base.Generator{SubArray{Any, 1, Vector{Any}, Tuple{UnitRange{Int64}}, true}, Catlab.Programs.RelationalPrograms.var"#15#16"}, #unused#::Base.EltypeUnknown, isz::Base.HasShape{1})
    @ Base ./array.jl:802
  [5] collect_similar
    @ ./array.jl:711 [inlined]
  [6] map
    @ ./abstractarray.jl:3261 [inlined]
  [7] parse_relation_inferred_args(args::SubArray{Any, 1, Vector{Any}, Tuple{UnitRange{Int64}}, true})
    @ Catlab.Programs.RelationalPrograms ~/.julia/packages/Catlab/NhhmR/src/programs/RelationalPrograms.jl:239
  [8] parse_relation_call(call::Expr)
    @ Catlab.Programs.RelationalPrograms ~/.julia/packages/Catlab/NhhmR/src/programs/RelationalPrograms.jl:220
  [9] parse_relation_diagram(head::Expr, body::Expr)
    @ Catlab.Programs.RelationalPrograms ~/.julia/packages/Catlab/NhhmR/src/programs/RelationalPrograms.jl:155
 [10] top-level scope
    @ REPL[6]:1

Changing the syntax to the following compiles:

si_uwd = @relation (S, I) where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end

I still can't use oapply after this fix though:

using AlgebraicPetri,AlgebraicPetri.TypedPetri
using Catlab, Catlab.CategoricalAlgebra, Catlab.Programs
using Catlab.WiringDiagrams
using AlgebraicDynamics.UWDDynam

epi_transitions = LabelledPetriNet(
  [:Pop],
  :infection=>((:Pop, :Pop)=>(:Pop, :Pop)),
  :recovery=>(:Pop=>:Pop),
  :strata=>(:Pop=>:Pop)
)

si_uwd = @relation (S, I) where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end
si_acs = oapply_typed(epi_transitions, si_uwd, [:β])
si_lpn = dom(si_acs)

ir_uwd = @relation (I, R) where (I::Pop, R::Pop) begin
    recovery(I, R)
end
ir_acs = oapply_typed(epi_transitions, ir_uwd, [:γ])
ir_lpn = dom(ir_acs)

sir_uwd = @relation (S, I, R) where (S::Pop, I::Pop, R::Pop) begin
    si(S, I)
    ir(I, R)
end

oapply(sir_uwd, Dict(
    :si => si_lpn,
    :ir => ir_lpn,
))

Output:

ERROR: MethodError: no method matching oapply(::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, ::Vector{LabelledPetriNet}, ::Nothing)

Closest candidates are:
  oapply(::AbstractUWD, ::AbstractDict, ::Union{Nothing, AbstractDict}; hom_attr, ob_attr, kwargs...)
   @ Catlab ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:21
  oapply(::AbstractUWD, ::AbstractVector{<:StructuredMulticospan{L}}, ::Union{Nothing, AbstractVector}; return_colimit) where L
   @ Catlab ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:77
  oapply(::AbstractUWD, ::AbstractDict{S, R}) where {S, R<:AbstractResourceSharer}
   @ AlgebraicDynamics ~/.julia/packages/AlgebraicDynamics/s3uNa/src/uwd_dynam.jl:298
  ...

Stacktrace:
 [1] oapply(composite::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, hom_map::Dict{Symbol, LabelledPetriNet}, ob_map::Nothing; hom_attr::Symbol, ob_attr::Symbol, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Catlab.WiringDiagrams.WiringDiagramAlgebras ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:28
 [2] oapply(composite::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, hom_map::Dict{Symbol, LabelledPetriNet}, ob_map::Nothing)
   @ Catlab.WiringDiagrams.WiringDiagramAlgebras ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:21
 [3] oapply(composite::Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol}, hom_map::Dict{Symbol, LabelledPetriNet})
   @ Catlab.WiringDiagrams.WiringDiagramAlgebras ~/.julia/packages/Catlab/NhhmR/src/wiring_diagrams/Algebras.jl:21
 [4] top-level scope
   @ REPL[1]:1

si_lpn etc. remain LabelledPetriNets, though, so perhaps this isn't too surprising.

jpfairbanks commented 1 year ago

I changed the last expression to:

oapply(sir_uwd, Dict(
    :si => Open(si_lpn),
    :ir => Open(ir_lpn),
))

and now it works.

You need to give the subsystem their boundaries. By calling Open(lpn) with only the one argument, the default constructor makes the boundary be the whole state space, which is compatible with the UWDs that you specified.

sdwfrost commented 1 year ago

Thanks @jpfairbanks! Closing this for now - working code below.

using AlgebraicPetri,AlgebraicPetri.TypedPetri
using Catlab, Catlab.CategoricalAlgebra, Catlab.Programs
using Catlab.WiringDiagrams
using AlgebraicDynamics.UWDDynam

epi_transitions = LabelledPetriNet(
  [:Pop],
  :infection=>((:Pop, :Pop)=>(:Pop, :Pop)),
  :recovery=>(:Pop=>:Pop),
  :strata=>(:Pop=>:Pop)
)

si_uwd = @relation (S, I) where (S::Pop, I::Pop) begin
    infection(S, I, I, I)
end
si_acs = oapply_typed(epi_transitions, si_uwd, [:β])
si_lpn = dom(si_acs)

ir_uwd = @relation (I, R) where (I::Pop, R::Pop) begin
    recovery(I, R)
end
ir_acs = oapply_typed(epi_transitions, ir_uwd, [:γ])
ir_lpn = dom(ir_acs)

sir_uwd = @relation (S, I, R) where (S::Pop, I::Pop, R::Pop) begin
    si(S, I)
    ir(I, R)
end

sir_smc = oapply(sir_uwd, Dict(
    :si => Open(si_lpn),
    :ir => Open(ir_lpn),
))

sir_lpn = apex(sir_smc)