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

Algebraic Data Type version of AMR #50

Closed
wants to merge 11 commits into from
Closed

Algebraic Data Type version of AMR #50

wants to merge 11 commits into from

Conversation

jpfairbanks
Copy link
Collaborator

I partially formalized the AMR representation for Petri nets as an ADT with MLStyle.jl and created some examples. This should enable:

  1. A consistent implementation of AMR between AlgebraicJulia and MTK
  2. A consistent specification between Julia implementations and other languages (Python 3.10 has structural pattern matching)
  3. A truly human readable syntax for models (see below)
  4. Automated deserialization of models using ACSet ADTs
  5. Automated generation of JSON schemas from a more concise specification

I don't expect anyone to adopt this before the evaluation event, unless they want to.

The human readable model specification could be in a DSL like

"""
ASKE Model Representation: [email protected] :: petrinet 
   amr-schemas:petri_schema.json

The SIR Model of disease
"""
Model = quote
LabelledPetriNet{Symbol} begin 
  S(label=:S)
  S(label=:I)
  S(label=:R)
  T(label=:inf)
  T(label=:rec)
  I(is=:S,it=:inf)
  I(is=:I,it=:inf)
  I(is=:I,it=:rec)
  O(os=:I,it=:inf)
  O(os=:I,it=:inf)
  O(os=:R,it=:rec)
 end
end

ODE Equations: begin
t::Time{day}


# β -- the beta parameter
β::Parameter{1/(persons^2*day)} = 0.01 ~ U(0.001,0.02)

inf::Rate = S*I*β

# γ -- the gama parameter
γ::Parameter{1/(persons*day)} = 3.0 ~ U(1,200.0)

rec::Rate = I*γ

# S₀ -- the initial susceptible population
S₀::Parameter{persons} = 3.0e8 ~ U(1.0e6,4.0e6)

S₀::Initial = S₀

# I₀ -- the initial infected population
I₀::Parameter{persons} = 1.0 ~ U(1,1)

I₀::Initial = I₀

# R₀ -- the initial recovered population
R₀::Parameter{persons} = 0.0 ~ U(0,4)

R₀::Initial = R₀
end

This example is based on Julia syntax (so that I can trick Julia's Base.parse into doing the first level of parsing for me), but we could also make a python version that is compatible with Python ast module or roll our own grammar from scratch.

@jpfairbanks
Copy link
Collaborator Author

I added support for typing maps and fixed the string representation to have indentation

"""
ASKE Model Representation: [email protected] :: petrinet 
   amr-schemas:petri_schema.json

The SIR Model of disease
"""
Model = begin
  LabelledPetriNet{Symbol} begin 
    S(label=:S)
    S(label=:I)
    S(label=:R)
    T(label=:inf)
    T(label=:rec)
    I(is=:S,it=:inf)
    I(is=:I,it=:inf)
    I(is=:I,it=:rec)
    O(os=:I,it=:inf)
    O(os=:I,it=:inf)
    O(os=:R,it=:rec)
   end
end

ODE Equations: begin
  t::Time{day}
  
  
  # β -- the beta parameter
  β::Parameter{1/(persons^2*day)} = 0.01 ~ U(0.001,0.02)
  
  inf::Rate = S*I*β
  
  # γ -- the gama parameter
  γ::Parameter{1/(persons*day)} = 3.0 ~ U(1,200.0)
  
  rec::Rate = I*γ
  
  # S₀ -- the initial susceptible population
  S₀::Parameter{persons} = 3.0e8 ~ U(1.0e6,4.0e6)
  
  S₀::Initial = S₀
  
  # I₀ -- the initial infected population
  I₀::Parameter{persons} = 1.0 ~ U(1,1)
  
  I₀::Initial = I₀
  
  # R₀ -- the initial recovered population
  R₀::Parameter{persons} = 0.0 ~ U(0,4)
  
  R₀::Initial = R₀
end

Typing: begin
  Model = begin
    LabelledPetriNet{Symbol} begin 
      S(label=:Pop)
      T(label=:inf)
      T(label=:disease)
      T(label=:strata)
      I(is=:Pop,it=:inf)
      I(is=:Pop,it=:inf)
      I(is=:Pop,it=:disease)
      I(is=:Pop,it=:strata)
      O(os=:Pop,it=:inf)
      O(os=:Pop,it=:inf)
      O(os=:Pop,it=:disease)
      O(os=:Pop,it=:strata)
     end
  end
TypeMap = [
  S => Pop,
  I => Pop,
  R => Pop,
  inf => inf,
  rec => disease,]
end

@jpfairbanks
Copy link
Collaborator Author

jpfairbanks commented Jul 2, 2023

I tweaked the output format to be slightly more machine readable as a proof of concept for parsing AMRs from human writable syntax. This format is now available as input and output.

"""
ASKE Model Representation: SIR Model0.1 :: petrinet 
   https://raw.githubusercontent.com/DARPA-ASKEM/Model-Representations/petrinet_v0.5/petrinet/petrinet_schema.json

Typed SIR model created by Nelson, derived from the one by Ben, Micah, Brandon
"""
Model = begin
  AMRPetriNet begin 
    S(id=S,name=Susceptible,units=nothing)
    S(id=I,name=Infected,units=nothing)
    S(id=R,name=Recovered,units=nothing)
    T(id=inf,name=Infection,desc=Infective process between individuals)
    T(id=rec,name=Recovery,desc=Recovery process of a infected individual)
    I(is=S,it=inf)
    I(is=I,it=inf)
    I(is=I,it=rec)
    O(os=I,ot=inf)
    O(os=I,ot=inf)
    O(os=R,ot=rec)
   end
end

ODE_Equations = begin
  inf::Rate = S * I * beta
  rec::Rate = I * gamma
  
  # β-- infection rate
  beta::Parameter{} = 0.027 ~ U(0,1)
  
  
  # γ-- recovery rate
  gamma::Parameter{} = 0.14 ~ U(0,1)
  
  
  # S₀-- Total susceptible population at timestep 0
  S0::Parameter{} = 1000.0 ~ δ(missing)
  
  
  # I₀-- Total infected population at timestep 0
  I0::Parameter{} = 1.0 ~ δ(missing)
  
  
  # R₀-- Total recovered population at timestep 0
  R0::Parameter{} = 0.0 ~ δ(missing)
  
  t::Time{day}
  
end

Typing = begin
  Model = begin
    AMRPetriNet begin 
      S(id=Pop,name=Pop,units=nothing)
      S(id=Vaccine,name=Vaccine,units=nothing)
      T(id=Infect,name=Infect,desc=2-to-2 process that represents infectious contact between two human individuals.)
      T(id=Disease,name=Disease,desc=1-to-1 process that represents a change in th edisease status of a human individual.)
      T(id=Strata,name=Strata,desc=1-to-1 process that represents a change in the demographic division of a human individual.)
      T(id=Vaccinate,name=Vaccinate,desc=2-to-1 process that represents an human individual receiving a vaccine dose.)
      T(id=Produce_Vaccine,name=Produce Vaccine,desc=0-to-1 process that represents the production of a single vaccine dose.)
      I(is=Pop,it=Infect)
      I(is=Pop,it=Infect)
      I(is=Pop,it=Disease)
      I(is=Pop,it=Strata)
      I(is=Pop,it=Vaccinate)
      I(is=Vaccine,it=Vaccinate)
      O(os=Pop,ot=Infect)
      O(os=Pop,ot=Infect)
      O(os=Pop,ot=Disease)
      O(os=Pop,ot=Strata)
      O(os=Pop,ot=Vaccinate)
      O(os=Vaccine,ot=Produce_Vaccine)
     end
  end
TypeMap = [
  S => Pop,
  I => Pop,
  R => Pop,
  inf => Infect,
  rec => Disease,]
end

TODO: ACSetSpec needs to wrap string attributes with "" on the way out, so that they can be read back in by the julia parser.

@jpfairbanks
Copy link
Collaborator Author

We had good discussions at the Hackathon week. @Free-Quarks, @JeffBezanson, @djinnome @JosephCottam @olynch, @liunelson, @fivegrant all liked the idea of using more structured representations for the models than just JSON objects and dictionary access. I know that @Free-Quarks has a rust implementation of the analogous data types. If a typescript implementation would be helpful for Uncharted, I think that would also be a good thing. Since different programming languages have different conventions for structuring repositories. We could do a AMR.jl, AMR.rs, AMR.py, and AMR.ts packages in separate repos or put them all as subfolders inside this repo.

The Julia package manager supports multiple packages being registered from within the same repo. So I would prefer making julia/AMR a directory in this repo and registering it so that we can do semantic versioning. @Free-Quarks, does Cargo allow something similar with rust/AMR?

We could also use our existing AlgebraicTemplate system to make a new julia package for AMR with its CICD and everything standardized to the AlgebraicJulia procedures.

Let me know what you all want to do repo-wise and we can start working on this after the PI meeting.

@Free-Quarks
Copy link

I think Cargo would allow something like this and it's something I can look into more. I'll pull @adarshp into this conversation too.

@adarshp
Copy link

adarshp commented Jul 19, 2023

@mwdchang
Copy link
Member

Note Terarium has structured/typed AMR defined here: https://github.com/DARPA-ASKEM/Terarium/blob/main/packages/client/hmi-client/src/types/Types.ts

@jpfairbanks
Copy link
Collaborator Author

It looks like Typescript has union types https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types

So we could use the same ADT in Rust, Julia, and Typescript if we limit to Record Types (structs) and Sum Types.

Enums in Rust, UnionTypes in typescript, and Union in Julia all seem to work the same way. Structs also work the same way, so we should be good to go in maintaining the harmonized ADT in the different language specific libraries.

@jpfairbanks
Copy link
Collaborator Author

This PR has moved to its own repo https://github.com/AlgebraicJulia/SyntacticModels.jl please provide any feedback on that repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants