Browse Source

Started migrating to the new UWD and @relation syntax (#26)

* Started migrating to the new UWD and @relation syntax

* Finished updating examples to show the UWD syntax

* Updated COEXIST code slightly

* Updated COEXIST model to use relation macro and the new bundle_legs function

* Removed need for Petri.jl

* Updated Catlab version

* Added more tests for coverage

* version bump to v0.5.2

* Fix version number
pull/28/head
Micah Halter 2 months ago
committed by GitHub
parent
commit
a0fba20453
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 224 additions and 353 deletions
  1. +2
    -2
      Project.toml
  2. +11
    -5
      examples/covid/chime/chime.jl
  3. +103
    -200
      examples/covid/coexist/coexist.jl
  4. +26
    -41
      examples/covid/epidemiology.jl
  5. +27
    -67
      examples/predation/lotka-volterra.jl
  6. +14
    -6
      src/AlgebraicPetri.jl
  7. +13
    -30
      src/Epidemiology.jl
  8. +9
    -2
      test/epidemiology.jl
  9. +18
    -0
      test/petri.jl
  10. +1
    -0
      test/runtests.jl

+ 2
- 2
Project.toml View File

@ -2,7 +2,7 @@ name = "AlgebraicPetri"
uuid = "4f99eebe-17bf-4e98-b6a1-2c4f205a959b"
license = "MIT"
authors = ["Micah Halter <micah@mehalter.com>"]
version = "0.5.1"
version = "0.6.0"
[deps]
AutoHashEquals = "15f4f7f2-30c1-5605-9d31-71845cf9641f"
@ -14,7 +14,7 @@ StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
[compat]
AutoHashEquals = "^0.2.0"
Catlab = "^0.9.1"
Catlab = "^0.9.3"
LabelledArrays = "^1"
Petri = "^1.2.3"
StatsBase = "^0.33"


+ 11
- 5
examples/covid/chime/chime.jl View File

@ -1,19 +1,25 @@
using AlgebraicPetri
using AlgebraicPetri.Epidemiology
using OrdinaryDiffEq
using LabelledArrays
using Plots
using Catlab.Theories
using Catlab.CategoricalAlgebra.FreeDiagrams
using Catlab.Graphics
using Catlab.CategoricalAlgebra
using Catlab.Programs.RelationalPrograms
display_wd(ex) = to_graphviz(ex, orientation=LeftToRight, labels=true);
display_uwd(ex) = to_graphviz(ex, box_labels=:name, junction_labels=:variable, edge_attrs=Dict(:len=>".75"));
sir = transmission recovery
sir = @relation (s, i, r) where (s, i, r) begin
infection(s, i)
recovery(i, r)
end
display_uwd(sir)
p_sir = apex(F_epi(sir));
display_wd(sir)
#-
p_sir = apex(oapply_epi(sir));
Graph(p_sir)
u0 = LVector(S=990, I=10, R=0);


+ 103
- 200
examples/covid/coexist/coexist.jl View File

@ -3,46 +3,36 @@
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/examples/covid/coexist/coexist.ipynb)
using AlgebraicPetri
using AlgebraicPetri.Epidemiology
using Petri
using LabelledArrays
using OrdinaryDiffEq
using Plots
using JSON
using Catlab
using Catlab.Theories
using Catlab.CategoricalAlgebra
using Catlab.Graphics
using Catlab.Programs
using Catlab.Theories
using Catlab.WiringDiagrams
using Catlab.Graphics
using Catlab.Graphics.Graphviz: run_graphviz
display_wd(ex) = to_graphviz(ex, orientation=LeftToRight, labels=true);
# Define some helper types where transition rates are
# real numbers and populations are natural numbers
const EpiRxnNet = LabelledReactionNet{Number,Int};
const OpenEpiRxnNet = OpenLabelledReactionNet{Number,Int};
const OpenEpiRxnNetOb = OpenLabelledReactionNetOb{Number,Int};
display_uwd(ex) = to_graphviz(ex, box_labels=:name, junction_labels=:variable, edge_attrs=Dict(:len=>"1"));
# Define helper functions for defining the two types of
# reactions in an epidemiology Model. Either a state
# spontaneously changes, or one state causes another to change
ob(x::Symbol,n::Int) = codom(Open([x], EpiRxnNet(x=>n), [x]));
ob(x::Symbol,n::Int) = codom(Open([x], LabelledReactionNet{Number,Int}(x=>n), [x])).ob;
function spontaneous_petri(transition::Symbol, rate::Number,
s::Symbol, s₀::Int,
t::Symbol, t₀::Int)
Open([s], EpiRxnNet((s=>s₀,t=>t₀), (transition,rate)=>(s=>t)), [t])
Open(LabelledReactionNet{Number,Int}(unique((s=>s₀,t=>t₀)), (transition,rate)=>(s=>t)))
end;
function exposure_petri(transition::Symbol, rate::Number,
s::Symbol, s₀::Int,
e::Symbol, e::Int,
t::Symbol, t₀::Int)
Open([s, e], EpiRxnNet((s=>s₀,e=>e,t=>t₀), (transition,rate)=>((s,e)=>(t,e))), [t])
Open(LabelledReactionNet{Number,Int}(unique((s=>s₀,e=>e,t=>t₀)), (transition,rate)=>((s,e)=>(t,e))))
end;
# Set arrays of initial conditions and rates to use in functor
@ -62,194 +52,112 @@ social_mixing_rate =
fatality_rate = [0.00856164, 0.03768844, 0.02321319, 0.04282494, 0.07512237, 0.12550367, 0.167096 , 0.37953452, 0.45757006];
# Extend the infectious disease presentation to handle the more
# complex version of SEIRD that the COEXIST model uses with
# asymptomatic infection, multiple stages of infection, and
# multiple stages of recovery
@present EpiCoexist <: InfectiousDiseases begin
I2::Ob
A::Ob
R2::Ob
exposure_e::Hom(S⊗E,E)
exposure_i2::Hom(S⊗I2,E)
exposure_a::Hom(S⊗A,E)
progression::Hom(I,I2)
asymptomatic_infection::Hom(E,A)
recover_late::Hom(R,R2)
asymptomatic_recovery::Hom(A,R)
recovery2::Hom(I2,R)
death2::Hom(I2,D)
end;
S,E,I,R,D,I2,A,R2,transmission,exposure,illness,recovery,death,exposure_e,exposure_i2,exposure_a,progression,asymptomatic_infection,recover_late,asymptomatic_recovery,recovery2, death2 = generators(EpiCoexist);
# Define a functor from the presentation to the building block
# Petri nets that define these operations
F(ex, n) = functor((OpenEpiRxnNetOb, OpenEpiRxnNet), ex, generators=Dict(
S=>ob(Symbol(:S, n), pop[n]),
E=>ob(Symbol(:E, n), 1000),
A=>ob(Symbol(:A, n), 1000),
I=>ob(Symbol(:I, n), 1000),
I2=>ob(Symbol(:I2, n), 1000),
R=>ob(Symbol(:R, n), 0),
R2=>ob(Symbol(:R2, n), 0),
D=>ob(Symbol(:D, n), 0),
transmission=>exposure_petri(:transmission, 0, :S, 0, :I, 0, :I, 0),
exposure=>exposure_petri(Symbol(:exp_, n), 1*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:I,n), 1000, Symbol(:E,n), 1000),
exposure_e=>exposure_petri(Symbol(:exp_e, n), .01*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:E,n),1000, Symbol(:E,n),1000),
exposure_i2=>exposure_petri(Symbol(:exp_i2, n), 6*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:I2,n), 1000, Symbol(:E,n),1000),
exposure_a=>exposure_petri(Symbol(:exp_a, n), 5*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:A,n),1000, Symbol(:E,n),1000),
progression=>spontaneous_petri(Symbol(:prog_, n), .25, Symbol(:I,n), 1000, Symbol(:I2,n), 1000),
asymptomatic_infection=>spontaneous_petri(Symbol(:asymp_, n), .86/.14*.2, Symbol(:E,n), 1000, Symbol(:A,n), 1000),
illness=>spontaneous_petri(Symbol(:ill_, n), .2, Symbol(:E,n), 1000, Symbol(:I,n), 1000),
asymptomatic_recovery=>spontaneous_petri(Symbol(:arec_, n), 1/15, Symbol(:A,n), 1000, Symbol(:R,n), 0),
recovery=>spontaneous_petri(Symbol(:rec_, n), 0, Symbol(:I,n), 0, Symbol(:R,n), 0),
recovery2=>spontaneous_petri(Symbol(:rec_, n), 1/6, Symbol(:I2,n), 1000, Symbol(:R,n), 0),
recover_late=>spontaneous_petri(Symbol(:rec2_, n), 1/15, Symbol(:R,n), 0, Symbol(:R2,n), 0),
death=>spontaneous_petri(Symbol(:death_, n), 0, Symbol(:I,n), 0, Symbol(:D,n), 0),
death2=>spontaneous_petri(Symbol(:death2_, n), (1/15)*(fatality_rate[n]/(1-fatality_rate[n])), Symbol(:I2,n), 1000, Symbol(:D,n), 0)));
# Define the COEXIST model using the `@program` macro
coexist = @program EpiCoexist (s::S, e::E, i::I, i2::I2, a::A, r::R, r2::R2, d::D) begin
e_2 = exposure(s, i)
e_3 = exposure_i2(s, i2)
e_4 = exposure_a(s, a)
e_5 = exposure_e(s, e)
e_all = [e, e_2, e_3, e_4, e_5]
a_2 = asymptomatic_infection(e_all)
a_all = [a, a_2]
r_2 = asymptomatic_recovery(a_all)
i_2 = illness(e_all)
i_all = [i, i_2]
i2_2 = progression(i)
i2_all = [i2, i2_2]
d_2 = death2(i2_all)
r_3 = recovery2(i2_all)
r_all = [r, r_2, r_3]
r2_2 = recover_late(r_all)
r2_all = [r2, r2_2]
d_all = [d, d_2]
return s, e_all, i_all, i2_all, a_all, r_all, r2_all, d_all
# Define an `oapply` function that connects the building block Petri nets to
# the operations we will use in the model.
F(ex, n) = oapply(ex, Dict(
:exposure=>exposure_petri(Symbol(:exp_, n), 1*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:I,n), 1000, Symbol(:E,n), 1000),
:exposure_e=>exposure_petri(Symbol(:exp_e, n), .01*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:E,n),1000, Symbol(:E,n),1000),
:exposure_i2=>exposure_petri(Symbol(:exp_i2, n), 6*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:I2,n), 1000, Symbol(:E,n),1000),
:exposure_a=>exposure_petri(Symbol(:exp_a, n), 5*social_mixing_rate[n][n]/pop[n], Symbol(:S,n), pop[n], Symbol(:A,n),1000, Symbol(:E,n),1000),
:progression=>spontaneous_petri(Symbol(:prog_, n), .25, Symbol(:I,n), 1000, Symbol(:I2,n), 1000),
:asymptomatic_infection=>spontaneous_petri(Symbol(:asymp_, n), .86/.14*.2, Symbol(:E,n), 1000, Symbol(:A,n), 1000),
:illness=>spontaneous_petri(Symbol(:ill_, n), .2, Symbol(:E,n), 1000, Symbol(:I,n), 1000),
:asymptomatic_recovery=>spontaneous_petri(Symbol(:arec_, n), 1/15, Symbol(:A,n), 1000, Symbol(:R,n), 0),
:recovery=>spontaneous_petri(Symbol(:rec_, n), 1/6, Symbol(:I2,n), 1000, Symbol(:R,n), 0),
:recover_late=>spontaneous_petri(Symbol(:rec2_, n), 1/15, Symbol(:R,n), 0, Symbol(:R2,n), 0),
:death=>spontaneous_petri(Symbol(:death2_, n), (1/15)*(fatality_rate[n]/(1-fatality_rate[n])), Symbol(:I2,n), 1000, Symbol(:D,n), 0)));
# Define the COEXIST model using the `@relation` macro
coexist = @relation (s, e, i, i2, a, r, r2, d) begin
exposure(s, i, e)
exposure_i2(s, i2, e)
exposure_a(s, a, e)
exposure_e(s, e, e)
asymptomatic_infection(e, a)
asymptomatic_recovery(a, r)
illness(e, i)
progression(i, i2)
death(i2, d)
recovery(i2, r)
recover_late(r, r2)
end;
coexist = to_hom_expr(FreeBiproductCategory, coexist);
display_wd(coexist)
display_uwd(coexist)
# Define a new presentation and functor to use in modeling
# Define an `oapply` function that can be used to create a model of
# cross exposure between two sets of populations
@present EpiCrossExposure(FreeBiproductCategory) begin
(S,E,A,I,I2,R,R2,D)::Ob
(S′,E′,A′,I,I2′,R′,R2′,D′)::Ob
exposure::Hom(S⊗I′,E)
exposure_e::Hom(S⊗E′,E)
exposure_a::Hom(S⊗A′,E)
exposure_i2::Hom(S⊗I2′,E)
exposure′::Hom(S′⊗I,E′)
exposure_e′::Hom(S′⊗E,E′)
exposure_a′::Hom(S′⊗A,E′)
exposure_i2′::Hom(S′⊗I2,E′)
end;
ce_S,ce_E,ce_A,ce_I,ce_I2,ce_R,ce_R2,ce_D, ce_S′,ce_E′,ce_A′,ce_I′,ce_I2′,ce_R′,ce_R2′,ce_D′, ce_exposure, ce_exposure_e, ce_exposure_a, ce_exposure_i2, ce_exposure′, ce_exposure_e′, ce_exposure_a′, ce_exposure_i2′ = generators(EpiCrossExposure);
F_cx(ex, x,y) = functor((OpenEpiRxnNetOb, OpenEpiRxnNet), ex, generators=Dict(
ce_S=>ob(Symbol(:S, x), pop[x]),
ce_E=>ob(Symbol(:E, x), 1000),
ce_A=>ob(Symbol(:A, x), 1000),
ce_I=>ob(Symbol(:I, x), 1000),
ce_I2=>ob(Symbol(:I2, x), 1000),
ce_R=>ob(Symbol(:R, x), 0),
ce_R2=>ob(Symbol(:R2, x), 0),
ce_D=>ob(Symbol(:D, x), 0),
ce_S′=>ob(Symbol(:S, y), pop[y]),
ce_E′=>ob(Symbol(:E, y), 1000),
ce_A′=>ob(Symbol(:A, y), 1000),
ce_I′=>ob(Symbol(:I, y), 1000),
ce_I2′=>ob(Symbol(:I2, y), 1000),
ce_R′=>ob(Symbol(:R, y), 0),
ce_R2′=>ob(Symbol(:R2, y), 0),
ce_D′=>ob(Symbol(:D, y), 0),
ce_exposure=>exposure_petri(Symbol(:exp_, x,y), 1*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:I,y), 1000, Symbol(:E,x), 1000),
ce_exposure_e=>exposure_petri(Symbol(:exp_e, x,y), .01*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:E,y),1000, Symbol(:E,x),1000),
ce_exposure_a=>exposure_petri(Symbol(:exp_a, x,y), 5*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:A,y),1000, Symbol(:E,x),1000),
ce_exposure_i2=>exposure_petri(Symbol(:exp_i2, x,y), 6*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:I2,y), 1000, Symbol(:E,x),1000),
ce_exposure′=>exposure_petri(Symbol(:exp_, y,x), 1*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:I,x), 1000, Symbol(:E,y), 1000),
ce_exposure_e′=>exposure_petri(Symbol(:exp_e, y,x), .01*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:E,x),1000, Symbol(:E,y),1000),
ce_exposure_a′=>exposure_petri(Symbol(:exp_a, y,x), 5*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:A,x),1000, Symbol(:E,y),1000),
ce_exposure_i2′=>exposure_petri(Symbol(:exp_i2, y,x), 6*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:I2,x), 1000, Symbol(:E,y),1000)
));
F_cx(ex, x,y) = oapply(ex, Dict(
:exposure=>exposure_petri(Symbol(:exp_, x,y), 1*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:I,y), 1000, Symbol(:E,x), 1000),
:exposure_e=>exposure_petri(Symbol(:exp_e, x,y), .01*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:E,y),1000, Symbol(:E,x),1000),
:exposure_a=>exposure_petri(Symbol(:exp_a, x,y), 5*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:A,y),1000, Symbol(:E,x),1000),
:exposure_i2=>exposure_petri(Symbol(:exp_i2, x,y), 6*social_mixing_rate[x][y]/(pop[x]+pop[y]), Symbol(:S,x), pop[x], Symbol(:I2,y), 1000, Symbol(:E,x),1000),
:exposure′=>exposure_petri(Symbol(:exp_, y,x), 1*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:I,x), 1000, Symbol(:E,y), 1000),
:exposure_e′=>exposure_petri(Symbol(:exp_e, y,x), .01*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:E,x),1000, Symbol(:E,y),1000),
:exposure_a′=>exposure_petri(Symbol(:exp_a, y,x), 5*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:A,x),1000, Symbol(:E,y),1000),
:exposure_i2′=>exposure_petri(Symbol(:exp_i2, y,x), 6*social_mixing_rate[y][x]/(pop[x]+pop[y]), Symbol(:S,y), pop[y], Symbol(:I2,x), 1000, Symbol(:E,y),1000)
),
Dict(
:s=>ob(Symbol(:S, x), pop[x]),
:e=>ob(Symbol(:E, x), 1000),
:a=>ob(Symbol(:A, x), 1000),
:i=>ob(Symbol(:I, x), 1000),
:i2=>ob(Symbol(:I2, x), 1000),
:r=>ob(Symbol(:R, x), 0),
:r2=>ob(Symbol(:R2, x), 0),
:d=>ob(Symbol(:D, x), 0),
:s′=>ob(Symbol(:S, y), pop[y]),
:e=>ob(Symbol(:E, y), 1000),
:a′=>ob(Symbol(:A, y), 1000),
:i′=>ob(Symbol(:I, y), 1000),
:i2′=>ob(Symbol(:I2, y), 1000),
:r′=>ob(Symbol(:R, y), 0),
:r2′=>ob(Symbol(:R2, y), 0),
:d′=>ob(Symbol(:D, y), 0)
));
# Use this new presentation to define a model
# of cross exposure between two populations
crossexposure = @program EpiCrossExposure (s::S, e::E, i::I, i2::I2, a::A, r::R, r2::R2, d::D,
s′::S′, e::E′, i′::I, i2′::I2′, a′::A′, r′::R′, r2′::R2′, d′::D′) begin
e_2 = exposure(s, i′)
e_3 = exposure_i2(s, i2′)
e_4 = exposure_a(s, a′)
e_5 = exposure_e(s, e)
e_all = [e, e_2, e_3, e_4, e_5]
e′_2 = exposure′(s′, i)
e′_3 = exposure_i2′(s′, i2)
e′_4 = exposure_a′(s′, a)
e′_5 = exposure_e′(s′, e_all)
e′_all = [e, e′_2, e′_3, e′_4, e′_5]
return s, e_all, i, i2, a, r, r2, d,
s′, e′_all, i′, i2′, a′, r′, r2′, d′
crossexposure = @relation (s, e, i, i2, a, r, r2, d, s′, e, i′, i2′, a′, r′, r2′, d′) begin
exposure(s, i′, e)
exposure_i2(s, i2′, e)
exposure_a(s, a′, e)
exposure_e(s, e, e)
exposure′(s′, i, e)
exposure_i2′(s′, i2, e)
exposure_a′(s′, a, e)
exposure_e′(s′, e, e)
end;
crossexposure = to_hom_expr(FreeBiproductCategory, crossexposure);
display_wd(crossexposure)
# To combine these two models, we need a final presentation
# that enables us to model 3 populations, each with their
# own COEXIST model, and interact through cross exposure
@present ThreeCoexist(FreeBiproductCategory) begin
(Pop1,Pop2,Pop3)::Ob
crossexp12::Hom(Pop1⊗Pop2,Pop1⊗Pop2)
crossexp13::Hom(Pop1⊗Pop3,Pop1⊗Pop3)
crossexp23::Hom(Pop2⊗Pop3,Pop2⊗Pop3)
coex1::Hom(Pop1,Pop1)
coex2::Hom(Pop2,Pop2)
coex3::Hom(Pop3,Pop3)
end;
Pop1, Pop2, Pop3, crossexp12, crossexp13, crossexp23, coex1, coex2, coex3 = generators(ThreeCoexist);
F_tcx(ex) = functor((OpenEpiRxnNetOb, OpenEpiRxnNet), ex, generators=Dict(
Pop1=>F(otimes(S,E,I,I2,A,R,R2,D),3),
Pop2=>F(otimes(S,E,I,I2,A,R,R2,D),4),
Pop3=>F(otimes(S,E,I,I2,A,R,R2,D),5),
crossexp12=>F_cx(crossexposure,3,4),
crossexp13=>F_cx(crossexposure,3,5),
crossexp23=>F_cx(crossexposure,4,5),
coex1=>F(coexist,3),
coex2=>F(coexist,4),
coex3=>F(coexist,5)
));
# Use this presentation to define this
# three-generational COEXIST model
threeNCoexist = @program ThreeCoexist (pop1::Pop1, pop2::Pop2, pop3::Pop3) begin
pop1′, pop2′ = crossexp12(pop1, pop2)
pop1′′, pop3′ = crossexp13(pop1′, pop3)
pop2′′, pop3′′ = crossexp23(pop2′, pop3′)
return coex1(pop1′′), coex2(pop2′′), coex3(pop3′′)
display_uwd(crossexposure)
# To combine these two models, we need to create a final relational model and
# use the `bundle_legs` function in our `oapply` that enables us to model 3
# population wires instead of each individual state as a wire. Each of these
# populations has their own COEXIST model, and interact through cross exposure
bundled_cross(x,y) = bundle_legs(F_cx(crossexposure, x, y), [tuple([1:8;]...), tuple([9:16;]...)])
bundled_coex(x) = bundle_legs(F(coexist, x), [tuple([1:8;]...)])
F_tcx(ex) = oapply(ex, Dict(
:crossexp12=>bundled_cross(3,4),
:crossexp13=>bundled_cross(3,5),
:crossexp23=>bundled_cross(4,5),
:coex1=>bundled_coex(3),
:coex2=>bundled_coex(4),
:coex3=>bundled_coex(5)));
threeNCoexist = @relation (pop1, pop2, pop3) begin
crossexp12(pop1, pop2)
crossexp13(pop1, pop3)
crossexp23(pop2, pop3)
coex1(pop1)
coex2(pop2)
coex3(pop3)
end;
threeNCoexist = to_hom_expr(FreeBiproductCategory, threeNCoexist);
display_wd(threeNCoexist)
# Once this final model has been defined, we can use
# the functors to get the underlying Petri net
display_uwd(threeNCoexist)
threeNCoexist_algpetri = apex(F_tcx(threeNCoexist))
threeNCoexist_petri = Petri.Model(threeNCoexist_algpetri)
Graph(threeNCoexist_algpetri)
# We can JSON to convert this Petri net into an
@ -263,7 +171,7 @@ JSON.print(threeNCoexist_algpetri.tables)
# concentrations and rates.
tspan = (0.0,100.0);
prob = ODEProblem(threeNCoexist_petri,concentrations(threeNCoexist_algpetri),tspan,rates(threeNCoexist_algpetri));
prob = ODEProblem(vectorfield(threeNCoexist_algpetri),concentrations(threeNCoexist_algpetri),tspan,rates(threeNCoexist_algpetri));
sol = solve(prob,Tsit5());
plot(sol, xlabel="Time", ylabel="Number of people")
@ -276,16 +184,11 @@ plot(sol, xlabel="Time", ylabel="Number of people")
for i in 1:length(social_mixing_rate)
for j in 1:length(social_mixing_rate[1])
if i != j
social_mixing_rate[i][j] = social_mixing_rate[i][j] / 10;
else
social_mixing_rate[i][j] = social_mixing_rate[i][j] / 5;
end
social_mixing_rate[i][j] = social_mixing_rate[i][j] / (i != j ? 10 : 5);
end
end
threeNCoexist_algpetri = apex(F_tcx(threeNCoexist));
threeNCoexist_petri = Petri.Model(threeNCoexist_algpetri);
prob = ODEProblem(threeNCoexist_petri,concentrations(threeNCoexist_algpetri),tspan,rates(threeNCoexist_algpetri));
prob = ODEProblem(vectorfield(threeNCoexist_algpetri),concentrations(threeNCoexist_algpetri),tspan,rates(threeNCoexist_algpetri));
sol = solve(prob,Tsit5());
plot(sol, xlabel="Time", ylabel="Number of people")

+ 26
- 41
examples/covid/epidemiology.jl View File

@ -10,29 +10,24 @@ using OrdinaryDiffEq
using Plots
using Catlab
using Catlab.Theories
using Catlab.CategoricalAlgebra
using Catlab.WiringDiagrams
using Catlab.Graphics
using Catlab.Programs
using Catlab.WiringDiagrams
using Catlab.CategoricalAlgebra
using Catlab.Programs.RelationalPrograms
display_wd(ex) = to_graphviz(ex, orientation=LeftToRight, labels=true);
display_uwd(ex) = to_graphviz(ex, box_labels=:name, junction_labels=:variable, edge_attrs=Dict(:len=>".75"));
# #### SIR Model:
# define model
sir = transmission recovery
# get resulting petri net as a C-Set
cset_sir = apex(F_epi(sir));
display_wd(sir)
sir = @relation (s,i,r) begin
infection(s,i)
recovery(i,r)
end
display_uwd(sir)
#-
# Use Petri.jl to visualize the C-Set
Graph(cset_sir)
p_sir = apex(oapply_epi(sir))
Graph(p_sir)
# define initial states and transition rates, then
# create, solve, and visualize ODE problem
@ -42,7 +37,7 @@ p = LVector(inf=0.4, rec=0.4);
# The C-Set representation has direct support for generating a DiffEq vector field
prob = ODEProblem(vectorfield(cset_sir),u0,(0.0,7.5),p);
prob = ODEProblem(vectorfield(p_sir),u0,(0.0,7.5),p);
sol = solve(prob,Tsit5())
plot(sol)
@ -50,20 +45,14 @@ plot(sol)
# #### SEIR Model:
# define model
seir = @program InfectiousDiseases (s::S,i::I) begin
i2 = illness(exposure(s,i))
return recovery([i,i2])
seir = @relation (s,e,i,r) begin
exposure(s,i,e)
illness(e,i)
recovery(i,r)
end
seir = to_hom_expr(FreeBiproductCategory, seir)
# here we convert the C-Set decoration to a Petri.jl model
# to use its StochasticDifferentialEquations support
p_seir = apex(F_epi(seir));
display_wd(seir)
display_uwd(seir)
#-
p_seir = apex(oapply_epi(seir))
Graph(p_seir)
# define initial states and transition rates, then
@ -80,19 +69,15 @@ plot(sol)
# #### SEIRD Model:
# define model
seird = @program InfectiousDiseases (s::S,i::I) begin
i_all = [i, illness(exposure(s,i))]
return recovery(i_all), death(i_all)
seird = @relation (s,e,i,r,d) begin
exposure(s,i,e)
illness(e,i)
recovery(i,r)
death(i,d)
end
seird = to_hom_expr(FreeBiproductCategory, seird)
# get resulting petri net and visualize model
p_seird = apex(F_epi(seird));
display_wd(seird)
display_uwd(seird)
#-
p_seird = apex(oapply_epi(seird))
Graph(p_seird)
# define initial states and transition rates, then
@ -104,4 +89,4 @@ p = LVector(exp=0.9, ill=0.2, rec=0.5, death=0.1);
prob = ODEProblem(vectorfield(p_seird),u0,(0.0,15.0),p);
sol = solve(prob,Tsit5())
plot(sol)
plot(sol)

+ 27
- 67
examples/predation/lotka-volterra.jl View File

@ -8,49 +8,36 @@ using OrdinaryDiffEq
using Plots
using Catlab
using Catlab.Theories
using Catlab.Programs
using Catlab.CategoricalAlgebra.FreeDiagrams
using Catlab.WiringDiagrams
using Catlab.Graphics
using Catlab.WiringDiagrams
using Catlab.CategoricalAlgebra
using Catlab.Programs.RelationalPrograms
display_wd(ex) = to_graphviz(ex, orientation=LeftToRight, labels=true);
display_uwd(ex) = to_graphviz(ex, box_labels=:name, junction_labels=:variable, edge_attrs=Dict(:len=>".75"));
# #### Step 1: Define the building block Petri nets needed to construct the model
petriOb = codom(Open([1], PetriNet(1), [1]))
birth_petri = Open([1], PetriNet(1, (1, (1,1))), [1]);
birth_petri = Open(PetriNet(1, 1=>(1,1)));
Graph(birth_petri)
#-
predation_petri = Open([1,2], PetriNet(2, ((1,2), (2,2))), [2]);
predation_petri = Open(PetriNet(2, (1,2)=>(2,2)));
Graph(predation_petri)
#-
death_petri = Open([1], PetriNet(1, (1, ())), [1]);
death_petri = Open(PetriNet(1, 1=>()));
Graph(death_petri)
# #### Step 2: Define a presentation of the free biproduct category
# that encodes the domain specific information
@present Predation(FreeBiproductCategory) begin
prey::Ob
predator::Ob
birth::Hom(prey,prey)
predation::Hom(prey⊗predator,predator)
death::Hom(predator,predator)
end;
rabbits,wolves,birth,predation,death = generators(Predation);
F(ex) = functor((OpenPetriNetOb, OpenPetriNet), ex, generators=Dict(
rabbits=>petriOb,wolves=>petriOb,
birth=>birth_petri, predation=>predation_petri, death=>death_petri));
# #### Step 2: Generate models using a relational syntax
# #### Step 3: Generate models using the hom expression or program notations
lotka_volterra = (birth id(wolves)) predation death
lotka_petri = apex(F(lotka_volterra))
display_wd(lotka_volterra)
lotka_volterra = @relation (wolves, rabbits) begin
birth(rabbits)
predation(rabbits, wolves)
death(wolves)
end
display_uwd(lotka_volterra)
#-
lv_dict = Dict(:birth=>birth_petri, :predation=>predation_petri, :death=>death_petri);
lotka_petri = apex(oapply(lotka_volterra, lv_dict))
Graph(lotka_petri)
# Generate appropriate vector fields, define parameters, and visualize solution
@ -61,46 +48,19 @@ prob = ODEProblem(vectorfield(lotka_petri),u0,(0.0,100.0),p);
sol = solve(prob,Tsit5(),abstol=1e-8);
plot(sol)
# There is also a second syntax that is easier to write for programmers
# than the hom expression syntax. Here is an example of the same model
# as before along with a test of equivalency
lotka_volterra2 = @program Predation (r::prey, w::predator) begin
r_2 = birth(r)
w_2 = predation(r_2, w)
return death(w_2)
end
lotka_petri2 = apex(F(to_hom_expr(FreeBiproductCategory, lotka_volterra2)))
lotka_petri == lotka_petri2
# #### Step 4: Extend your presentation to handle more complex phenomena
# such as a small food chain
@present DualPredation <: Predation begin
Predator::Ob
Predation::Hom(predator⊗Predator,Predator)
Death::Hom(Predator,Predator)
end;
fish,Fish,Shark,birth,predation,death,Predation,Death = generators(DualPredation);
# #### Step 3: Extend your model to handle more complex phenomena
# such as a small food chain between little fish, big fish, and sharks
F(ex) = functor((OpenPetriNetOb, OpenPetriNet), ex, generators=Dict(
fish=>petriOb,Fish=>petriOb,
birth=>birth_petri, predation=>predation_petri, death=>death_petri,
Shark=>petriOb,Predation=>predation_petri, Death=>death_petri));
# Define a new model where fish are eaten by Fish which are then eaten by Sharks
dual_lv = @program DualPredation (fish::prey, Fish::predator, Shark::Predator) begin
f_2 = birth(fish)
F_2 = predation(f_2, Fish)
F_3 = death(F_2)
S_2 = Predation(F_3, Shark)
S_3 = Death(S_2)
dual_lv = @relation (fish, Fish, Shark) begin
birth(fish)
predation(fish, Fish)
predation(Fish, Shark)
death(Fish)
death(Shark)
end
display_wd(dual_lv)
display_uwd(dual_lv)
#-
dual_lv_petri = apex(F(to_hom_expr(FreeBiproductCategory, dual_lv)))
dual_lv_petri = apex(oapply(dual_lv, lv_dict))
Graph(dual_lv_petri)
# Generate a new solver, provide parameters, and analyze results
@ -109,4 +69,4 @@ u0 = [100, 10, 2];
p = [.3, .015, .7, .017, .35];
prob = ODEProblem(vectorfield(dual_lv_petri),u0,(0.0,100.0),p);
sol = solve(prob,Tsit5(),abstol=1e-6);
plot(sol)
plot(sol)

+ 14
- 6
src/AlgebraicPetri.jl View File

@ -47,7 +47,9 @@ const AbstractPetriNet = AbstractACSetType(TheoryPetriNet)
const PetriNet = CSetType(TheoryPetriNet,index=[:it,:is,:ot,:os])
const OpenPetriNetOb, OpenPetriNet = OpenCSetTypes(PetriNet,:S)
Open(n, p::AbstractPetriNet, m) = OpenPetriNet(p, FinFunction(n, ns(p)), FinFunction(m, ns(p)))
Open(p::AbstractPetriNet) = OpenPetriNet(p, map(x->FinFunction([x], ns(p)), 1:ns(p))...)
Open(p::AbstractPetriNet, legs...) = OpenPetriNet(p, map(l->FinFunction(l, ns(p)), legs)...)
Open(n, p::AbstractPetriNet, m) = Open(p, n, m)
# PetriNet([:S, :I, :R], :infection=>((1, 2), 3))
@ -145,10 +147,12 @@ const LabelledPetriNet = LabelledPetriNetUntyped{Symbol}
const OpenLabelledPetriNetObUntyped, OpenLabelledPetriNetUntyped = OpenACSetTypes(LabelledPetriNetUntyped,:S)
const OpenLabelledPetriNetOb, OpenLabelledPetriNet = OpenLabelledPetriNetObUntyped{Symbol}, OpenLabelledPetriNetUntyped{Symbol}
Open(n, p::AbstractLabelledPetriNet, m) = begin
Open(p::AbstractLabelledPetriNet) = OpenLabelledPetriNet(p, map(x->FinFunction([x], ns(p)), 1:ns(p))...)
Open(p::AbstractLabelledPetriNet, legs...) = begin
s_idx = Dict(sname(p, s)=>s for s in 1:ns(p))
OpenLabelledPetriNet(p, FinFunction(map(i->s_idx[i], n), ns(p)), FinFunction(map(i->s_idx[i], m), ns(p)))
OpenLabelledPetriNet(p, map(l->FinFunction(map(i->s_idx[i], l), ns(p)),legs)...)
end
Open(n, p::AbstractLabelledPetriNet, m) = Open(p, n, m)
LabelledPetriNet(n,ts...) = begin
p = LabelledPetriNet()
@ -180,7 +184,9 @@ const AbstractReactionNet = AbstractACSetType(TheoryReactionNet)
const ReactionNet = ACSetType(TheoryReactionNet, index=[:it,:is,:ot,:os])
const OpenReactionNetOb, OpenReactionNet = OpenACSetTypes(ReactionNet,:S)
Open(n, p::AbstractReactionNet{R,C}, m) where {R,C} = OpenReactionNet{R,C}(p, FinFunction(n, ns(p)), FinFunction(m, ns(p)))
Open(p::AbstractReactionNet{R,C}) where {R,C} = OpenReactionNet{R,C}(p, map(x->FinFunction([x], ns(p)), 1:ns(p))...)
Open(p::AbstractReactionNet{R,C}, legs...) where {R,C} = OpenReactionNet{R,C}(p, map(l->FinFunction(l, ns(p)), legs)...)
Open(n, p::AbstractReactionNet, m) = Open(p, n, m)
ReactionNet{R,C}(n,ts...) where {R,C} = begin
p = ReactionNet{R,C}()
@ -215,10 +221,12 @@ const OpenLabelledReactionNetObUntyped, OpenLabelledReactionNetUntyped = OpenACS
const OpenLabelledReactionNetOb{R,C} = OpenLabelledReactionNetObUntyped{R,C,Symbol}
const OpenLabelledReactionNet{R,C} = OpenLabelledReactionNetUntyped{R,C,Symbol}
Open(n, p::AbstractLabelledReactionNet{R,C}, m) where {R,C} = begin
Open(p::AbstractLabelledReactionNet{R,C}) where {R,C} = OpenLabelledReactionNet{R,C}(p, map(x->FinFunction([x], ns(p)), 1:ns(p))...)
Open(p::AbstractLabelledReactionNet{R,C}, legs...) where {R,C} = begin
s_idx = Dict(sname(p, s)=>s for s in 1:ns(p))
OpenLabelledReactionNet{R,C}(p, FinFunction(map(i->s_idx[i], n), ns(p)), FinFunction(map(i->s_idx[i], m), ns(p)))
OpenLabelledReactionNet{R,C}(p, map(l->FinFunction(map(i->s_idx[i], l), ns(p)), legs)...)
end
Open(n, p::AbstractLabelledReactionNet, m) = Open(p, n, m)
# Ex. LabelledReactionNet{Number, Int}((:S=>990, :I=>10, :R=>0), (:inf, .3/1000)=>((:S, :I)=>(:I,:I)), (:rec, .2)=>(:I=>:R))
LabelledReactionNet{R,C}(n,ts...) where {R,C} = begin


+ 13
- 30
src/Epidemiology.jl View File

@ -3,43 +3,26 @@ module Epidemiology
using AlgebraicPetri
using Catlab
using Catlab.Theories
using Catlab.WiringDiagrams
using Catlab.CategoricalAlgebra.FinSets
export InfectiousDiseases, FunctorGenerators, F_epi, S, E, I, R, D, transmission, exposure, illness, recovery, death
export oapply_epi, infection, exposure, illness, recovery, death
ob(x::Symbol) = codom(Open([x], LabelledPetriNet(x), [x]))
spontaneous_petri(x::Symbol, y::Symbol, z::Symbol) = Open([x], LabelledPetriNet([x,y], z=>(x, y)), [y])
transmission_petri = Open([:S], LabelledPetriNet([:S,:I], :inf=>((:S,:I)=>(:I,:I))), [:I])
exposure_petri = Open([:S, :I], LabelledPetriNet([:S,:I,:E], :exp=>((:S,:I)=>(:E,:I))), [:E])
spontaneous_petri(x::Symbol, y::Symbol, z::Symbol) = Open(LabelledPetriNet(unique([x,y]), z=>(x, y)))
exposure_petri(x::Symbol, y::Symbol, z::Symbol, transition::Symbol) =
Open(LabelledPetriNet(unique([x,y,z]), transition=>((x,y)=>(z,y))))
""" InfectiousDiseases
"""
@present InfectiousDiseases(FreeBiproductCategory) begin
S::Ob
E::Ob
I::Ob
R::Ob
D::Ob
transmission::Hom(S⊗I, I)
exposure::Hom(S⊗I, E)
illness::Hom(E,I)
recovery::Hom(I,R)
death::Hom(I,D)
end
infection = exposure_petri(:S, :I, :I, :inf)
exposure = exposure_petri(:S, :I, :E, :exp)
illness = spontaneous_petri(:E,:I,:ill)
recovery = spontaneous_petri(:I,:R,:rec)
death = spontaneous_petri(:I,:D,:death)
""" generators
"""
S,E,I,R,D,transmission,exposure,illness,recovery,death = generators(InfectiousDiseases);
""" FunctorGenerators
"""
const FunctorGenerators = Dict(S=>ob(:S), E=>ob(:E), I=>ob(:I), R=>ob(:R), D=>ob(:D),
transmission=>transmission_petri, exposure=>exposure_petri,
illness=>spontaneous_petri(:E,:I,:ill), recovery=>spontaneous_petri(:I,:R,:rec), death=>spontaneous_petri(:I,:D,:death))
epi_dict = Dict(:infection=>infection, :exposure=>exposure, :illness=>illness, :recovery=>recovery, :death=>death)
""" F_epi
""" oapply_epi
"""
F_epi(ex) = functor((OpenLabelledPetriNetOb, OpenLabelledPetriNet), ex, generators=FunctorGenerators)
oapply_epi(ex) = oapply(ex, epi_dict)
end


+ 9
- 2
test/epidemiology.jl View File

@ -1,5 +1,12 @@
sir_petri = LabelledPetriNet([:S,:I,:R], :inf=>((:S,:I)=>(:I,:I)), :rec=>(:I=>:R))
sir = transmission recovery
sir = infection recovery
@test apex(F_epi(sir)) == sir_petri
@test apex(sir) == sir_petri
sir_relation = @relation (s,i,r) begin
infection(s,i)
recovery(i,r)
end
@test apex(oapply_epi(sir_relation)) == sir_petri

+ 18
- 0
test/petri.jl View File

@ -17,3 +17,21 @@ h_id = h ⋅ id(OpenPetriNetOb(FinSet(1)))
@test h == h′
@test h == h_id
# Test open petri net notations either fully open or by specifying legs
pn = Open(PetriNet(3, ((1,2), 3)), [1], [2], [3])
pn′ = Open(PetriNet(3, ((1,2), 3)))
@test pn == pn′
lpn = Open(LabelledPetriNet([:I, :R], (:rec, :I=>:R)))
lpn′ = Open([:I], LabelledPetriNet([:I, :R], (:rec, :I=>:R)), [:R])
@test lpn == lpn′
rn = Open(ReactionNet{Number,Int}([10, 0], (.25, 1=>2)))
rn′ = Open([1], ReactionNet{Number,Int}([10, 0], (.25, 1=>2)), [2])
@test rn == rn′
lrn = Open(LabelledReactionNet{Number,Int}([:I=>10, :R=>0], ((:rec=>.25), :I=>:R)))
lrn′ = Open([:I], LabelledReactionNet{Number,Int}([:I=>10, :R=>0], ((:rec=>.25), :I=>:R)), [:R])
@test lrn == lrn′

+ 1
- 0
test/runtests.jl View File

@ -7,6 +7,7 @@ using AlgebraicPetri.Epidemiology
using Catlab.Theories
using Catlab.CategoricalAlgebra
using Catlab.CategoricalAlgebra.FinSets
using Catlab.Programs
@testset "Core" begin
include("core.jl")


Loading…
Cancel
Save