diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..c8fde90 --- /dev/null +++ b/Project.toml @@ -0,0 +1,5 @@ +[deps] +ACSets = "227ef7b5-1206-438b-ac65-934d6da304b8" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" diff --git a/src/julia/amr.jl b/src/julia/amr.jl new file mode 100644 index 0000000..25db0f7 --- /dev/null +++ b/src/julia/amr.jl @@ -0,0 +1,416 @@ +module AMR + +export Math, MathML, ExpressionFormula, Unit, Distribution, Observable, Expression, + Rate, Initial, Parameter, Time, + StandardUniform, Uniform, StandardNormal, Normal, PointMass, + Semantic, Header, ODERecord, ODEList, Typing, ASKEModel, + distro_string, amr_to_string + +using Reexport +@reexport using MLStyle +@reexport using ACSets +using ACSets.ADTs +using ACSets.ACSetInterface + +@data MathML begin + Math(String) + Presentation(String) +end + +nomath = Math("") + +@as_record struct ExpressionFormula{T} + expression::T + expression_mathml::MathML +end + +@as_record struct Unit + expression::String + expression_mathml::MathML +end + + +nounit = Unit("", nomath) + +@data Distribution begin + StandardUniform + Uniform(min, max) + StandardNormal + Normal(mean, variance) + PointMass(value) +end + +@as_record struct Observable{T} + id::Symbol + name::String + states::Vector{Symbol} + f::ExpressionFormula +end + +@data Expression begin + Rate(target::Symbol, f::ExpressionFormula) + Initial(target::Symbol, f::ExpressionFormula) + Parameter(id::Symbol, name::String, description::String, units::Unit, value::Float64, distribution::Distribution) + Time(id::Symbol, units::Unit) +end + +@data Semantic begin + ODEList(statements::Vector{Expression}) + ODERecord(rates::Vector{Rate}, initials::Vector{Initial}, parameters::Vector{Parameter}, time::Time) + # Metadata + Typing(system::ACSetSpec, map::Vector{Pair}) + # Stratification +end + +@as_record struct Header + name::String + schema::String + description::String + schema_name::String + model_version::String +end + +@as_record struct ASKEModel + header::Header + model::ACSetSpec + semantics::Vector{Semantic} +end + +function distro_string(d::Distribution) + @match d begin + StandardUniform => "U(0,1)" + Uniform(min, max) => "U($min,$max)" + StandardNormal => "N(0,1)" + Normal(mu, var) => "N($mu,$var)" + PointMass(value) => "δ($value)" + end +end + +function distro_expr(d::Distribution) + return Base.Meta.parse(distro_string(d)) +end + +padlines(ss::Vector, n) = map(ss) do s + " "^n * s +end +padlines(s::String, n=2) = join(padlines(split(s, "\n"), n), "\n") + +function amr_to_string(amr) + let ! = amr_to_string + @match amr begin + s::String => s + Math(s) => !s + Presentation(s) => " $s " + u::Unit => !u.expression + d::Distribution => distro_string(d) + Time(id, u) => "$id::Time{$(!u)}\n" + Rate(t, f) => "$t::Rate = $(f.expression)" + Initial(t, f) => "$t::Initial = $(f.expression)" + Observable(id, n, states, f) => "# $n\n$id::Observable = $(f.expression)($states)\n" + Header(name, s, d, sn, mv) => "\"\"\"\nASKE Model Representation: $name$mv :: $sn \n $s\n\n$d\n\"\"\"" + Parameter(t, n, d, u, v, dist) => "\n# $n-- $d\n$t::Parameter{$(!u)} = $v ~ $(!dist)\n" + m::ACSetSpec => "Model = begin\n$(padlines(ADTs.to_string(m),2))\nend" + ODEList(l) => "ODE_Equations = begin\n" * padlines(join(map(!, l), "\n")) * "\nend" + ODERecord(rts, init, para, time) => join(vcat(["ODE_Record = begin\n"], !rts , !init, !para, [!time, "end"]), "\n") + vs::Vector{Pair} => map(vs) do v; "$(v[1]) => $(v[2])," end |> x-> join(x, "\n") + vs::Vector{Semantic} => join(map(!, vs), "\n\n") + xs::Vector => map(!, xs) + Typing(system, map) => "Typing = begin\n$(padlines(!system, 2))\nTypeMap = [\n$(padlines(!map, 2))]\nend" + ASKEModel(h, m, s) => "$(!h)\n$(!m)\n\n$(!s)" + end + end +end + +block(exprs) = begin + q = :(begin + + end) + append!(q.args, exprs) + return q +end + +extract_acsetspec(s::String) = join(split(s, " ")[2:end], " ") |> Meta.parse + +function amr_to_expr(amr) + let ! = amr_to_expr + @match amr begin + s::String => s + Math(s) => :(Math($(!s))) + Presentation(s) => :(Presentation($(!s))) + u::Unit => u.expression + d::Distribution => distro_expr(d) + Time(id, u) => :($id::Time{$(!u)}) + Rate(t, f) => :($t::Rate = $(f.expression)) + Initial(t, f) => :($t::Initial = $(f.expression)) + Observable(id, n, states, f) => begin "$n"; :(@doc $x $id::Observable = $(f.expression)($states)) end + Header(name, s, d, sn, mv) => begin x = "ASKE Model Representation: $name$mv :: $sn \n $s\n\n$d"; :(@doc $x) end + Parameter(t, n, d, u, v, dist) => begin x = "$n-- $d"; :(@doc $x $t::Parameter{$(!u)} = $v ~ $(!dist)) end + m::ACSetSpec => :(Model = begin $(extract_acsetspec(ADTs.to_string(m))) end) + ODEList(l) => :(ODE_Equations = $(block(map(!, l)))) + ODERecord(rts, init, para, time) => :(ODE_Record = (rates=$(!rts), initials=$(!init), parameters=$(!para), time=!time)) + vs::Vector{Pair} => begin ys = map(vs) do v; :($(v[1]) => $(v[2])) end; block(ys) end + vs::Vector{Semantic} => begin ys = map(!, vs); block(ys) end + xs::Vector => begin ys = map(!, xs); block(ys) end + Typing(system, map) => :(Typing = $(!system); TypeMap = $(block(map))) + ASKEModel(h, m, s) => :($(!h);$(!m);$(!s)) + end + end +end + +optload(d, path, default=nothing) = begin + let ! = optload + @match path begin + s::Symbol => !(d, string(s), default) + s::String => get(d, s, default) + [addr] => !(d, addr, default) + [head, args...] => !(get(d, head, Dict()), args, default) + _=> error("Bad recursion in optload($d, $path)") + end + end +end + +function petrispec(dict::AbstractDict) + findkwarg(kwarg::Symbol, d::AbstractDict, path, default=nothing) = Kwarg(kwarg, Value(optload(d, path, default))) + loadstate(s) = begin + Statement(:S, [findkwarg(k, s, p, :nothing) for (k,p) in [(:id, "id"), (:name, "name"), (:units, ["units", "expression"])]]) + end + states = [loadstate(s) for s in dict["states"]] + transi = [ + Statement(:T, + [Kwarg(:id, Value(Symbol(t["id"]))), Kwarg(:name, Value(t["properties"]["name"])), Kwarg(:desc, Value(t["properties"]["description"])) ] + ) for t in dict["transitions"]] + + inputs = [[ + Statement(:I, + [Kwarg(:is, Value(i)), Kwarg(:it, Value(t["id"]))]) for i in t["input"]] for t in dict["transitions"] + ] |> Base.Flatten |> collect + outputs = [[ + Statement(:O, + [Kwarg(:os, Value(i)), Kwarg(:ot, Value(t["id"]))]) for i in t["output"]] for t in dict["transitions"] + ] |> Base.Flatten |> collect + ACSetSpec(:AMRPetriNet, vcat(states, transi, inputs, outputs)) +end + +function load(::Type{Unit}, d::AbstractDict) + ud = get(d, "units", Dict("expression"=>"", "expression_mathml"=>"")) + u = Unit(ud["expression"], Presentation(ud["expression_mathml"])) +end + +function load(::Type{Time}, t::AbstractDict) + Time(Symbol(t["id"]), load(Unit, t)) +end + +function load(::Type{Rate}, r::AbstractDict) + f = ExpressionFormula(r["expression"], Presentation(r["expression_mathml"])) + Rate(Symbol(r["target"]), f) +end + +function load(::Type{Initial}, d::AbstractDict) + f = ExpressionFormula(d["expression"], Presentation(d["expression_mathml"])) + Initial(Symbol(d["target"]), f) +end + +function load(::Type{Distribution}, d::AbstractDict) + @match d begin + Dict("type"=>"StandardUniform1") => StandardUniform + Dict("type"=>"StandardNormal") => StandardNormal + Dict("type"=>"Uniform", "parameters"=>p) => Uniform(p["minimum"], p["maximum"]) + Dict("type"=>"Uniform1", "parameters"=>p) => Uniform(p["minimum"], p["maximum"]) + Dict("type"=>"Normal", "parameters"=>p) => Normal(p["mu"], p["var"]) + Dict("type"=>"PointMass", "parameters"=>p) => PointMass(p["value"]) + end +end + +load(::Type{Distribution}, ::Nothing) = PointMass(missing) + + +function load(::Type{Parameter}, d::AbstractDict) + u = load(Unit, d) + Parameter( + Symbol(d["id"]), + d["name"], + d["description"], + u, + d["value"], + load(Distribution, get(d,"distribution", nothing)) + ) +end + +function load(::Type{ODERecord}, d::AbstractDict) + time = load(Time, d["time"]) + rate(x) = load(Rate, x) + initial(x) = load(Initial, x) + parameter(x) = load(Parameter, x) + rates = rate.(d["rates"]) + initials = initial.(d["initials"]) + parameters = parameter.(d["parameters"]) + + ODERecord(rates, initials, parameters, time) +end + +function load(::Type{Header}, d::AbstractDict) + @match d begin + Dict("name"=>n, "schema"=>s, "description"=>d, "schema_name"=>sn, "model_version"=>mv) => Header(n,s,d,sn,mv) + _ => error("Information for Header was not found in $d") + end +end + +function load(::Type{Typing}, d::AbstractDict) + @match d begin + Dict("type_system"=>s, "type_map"=>m) => begin @show m; Typing(petrispec(s), [x[1]=> x[2] for x in m]) end + _ => error("Typing judgement was not properly encoded in $d") + end +end + +function load(::Type{ASKEModel}, d::AbstractDict) + hdr = load(Header, d) + hdr.schema_name == "petrinet" || error("only petrinet models are supported") + mdl = petrispec(d["model"]) + sem = [] + if haskey(d["semantics"], "ode") + push!(sem, load(ODERecord, d["semantics"]["ode"])) + end + if haskey(d["semantics"], "typing") + push!(sem, load(Typing, d["semantics"]["typing"])) + end + ASKEModel(hdr, mdl, sem) +end + +using MLStyle.Modules.AST + +function load(::Type{Time}, ex::Expr) + @matchast ex quote + $a::Time{} => Time(a, Unit("", Math(""))) + $a::Time{$b} => Time(a, load(Unit, b)) + _ => error("Time was not properly encoded as Expr $ex") + end +end + +function load(::Type{Unit}, ex::Union{Symbol, Expr}) + Unit(string(ex), Math("")) +end + +function load(::Type{ExpressionFormula}, ex::Expr) + ExpressionFormula(string(ex), Math(string(ex))) +end + +function load(::Type{Rate}, ex::Expr) + @matchast ex quote + ($a::Rate = $ex) => Rate(a, load(ExpressionFormula, ex)) + ($a::Rate{$u} = $ex) => Rate(a, load(ExpressionFormula, ex)) + _ => error("Rate was not properly encoded as Expr $ex") + end +end + +function load(::Type{Initial}, ex::Expr) + @matchast ex quote + ($a::Initial = $ex) => Rate(a, load(ExpressionFormula, ex)) + ($a::Initial{$u} = $ex) => Rate(a, load(ExpressionFormula, ex)) + _ => error("Rate was not properly encoded as Expr $ex") + end +end + +function docval(exp::Expr) + s, ex = @match exp begin + Expr(:macrocall, var"@doc", _, s, ex) => (s,ex) + _ => error("Could not match documented value in $exp") + end + name, desc = split(s, "--") + return strip(name), strip(desc), ex +end + +function load(d::Type{Distribution}, ex::Expr) + @matchast ex quote + U(0,1) => StandardUniform + U($min,$max) => Uniform(min, max) + N(0,1) => StandardNormal + N($mu,$var) => Normal(mu, var) + δ($value) => PointMass(value) + _ => error("Failed to find distribution in $ex") + end +end + + +function load(::Type{Parameter}, ex::Expr) + name, desc, ex = docval(ex) + id, u, val, dist = @matchast ex quote + ($id::Parameter{} = ($val ~ $d)) => (id, nounit, val, load(Distribution, d)) + ($id::Parameter{$u} = ($val ~ $d)) => (id, load(Unit, u), val, load(Distribution, d)) + ($id::Parameter{} = $val) => (id, nounit, val, PointMass(missing)) + ($id::Parameter{$u} = $val) => (id, load(Unit, u), val, PointMass(missing)) + end + Parameter(id, name, desc, u, val, dist) +end + +function load(::Type{ODEList}, ex::Expr) + map(ex.args[2].args) do arg + try + return load(Rate, arg) + catch ErrorException + try + return load(Initial, arg) + catch ErrorException + try + return load(Parameter, arg) + catch ErrorException + try + return load(Time, arg) + catch + return nothing + end + end + end + end + end |> x->filter(!isnothing, x) |> ODEList +end + +function load(::Type{Header}, ex::String) + hdr, schema, _, desc, _ = split(ex, "\n") + hdr, schema_name = split(hdr, "::") + _, hdr = split(hdr, ":") + name, version = split(hdr, "@") + Header(strip(name), strip(schema), strip(desc), strip(schema_name), strip(version)) +end + +function load(::Type{ACSetSpec}, ex::Expr) + let ! = x->load(ACSetSpec, x) + @match ex begin + Expr(:(=), name, body) => @match body.args[2] begin + Expr(:(=), type, body) => acsetspec(type, body) + end + Expr(:block, lnn, body) => !(body) + _ => ex + end + end +end + +function load(::Type{Typing}, ex::Expr) + let !(x) = load(Typing, x) + @match ex begin + Expr(:(=), :Model, body) => load(ACSetSpec, ex) + Expr(:(=), :TypeMap, list) => !list + Expr(:(=), :Typing, body) => Typing(!(body.args[2]), !(body.args[4])) + Expr(:vect, args...) => map(args) do arg + @match arg begin + Expr(:call, :(=>), a, b) => Pair(a,b) + _ => error("The type map is expected to be pairs defined with a => fa. Got $arg") + end + end + _ => error("Could not processing Typing assignment from $ex") + end + end +end + +function load(::Type{ASKEModel}, ex::Expr) + elts = map(ex.args) do arg + @match arg begin + Expr(:macrocall, var"@doc", _, s, ex) => (load(Header, s), load(ACSetSpec, ex)) + Expr(:(=), :ODE_Record, body) => load(ODEList, arg) + Expr(:(=), :ODE_Equations, body) => load(ODEList, arg) + Expr(:(=), :Typing, body) => load(Typing, arg) + _ => arg + end + end + ASKEModel(elts[2][1], elts[2][2], [elts[4], elts[6]]) +end +end # module end \ No newline at end of file diff --git a/src/julia/amr_examples.jl b/src/julia/amr_examples.jl new file mode 100644 index 0000000..8e588bc --- /dev/null +++ b/src/julia/amr_examples.jl @@ -0,0 +1,551 @@ +module AMRExamples +include("amr.jl") +using .AMR +using Test + +nomath = Math("") +header = Header("SIR", "amr-schemas:petri_schema.json", "The SIR Model of disease", "petrinet", "0.2") +model = acsetspec(:(LabelledPetriNet{Symbol}), quote + 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) + +ode = ODERecord([Rate(:inf, ExpressionFormula( "S*I*β", nomath)), + Rate(:rec, ExpressionFormula("I*γ", nomath))], + + [Initial(:S, ExpressionFormula("S₀", nomath)), + Initial(:I, ExpressionFormula("I₀", nomath)), + Initial(:R, ExpressionFormula("R₀", nomath)),], + + [Parameter(:β, "β", "the beta parameter", Unit("1/(persons^2*day)", nomath), 1e-2, Uniform(1e-3, 2e-2)), + Parameter(:γ, "γ", "the gama parameter", Unit("1/(persons*day)", nomath), 3, Uniform(1, 2e+2)), + + Parameter(:S₀, "S₀", "the initial susceptible population", Unit("persons", nomath), 300000000.0, Uniform(1e6, 4e6)), + Parameter(:I₀, "I₀", "the initial infected population", Unit("persons", nomath), 1.0, Uniform(1, 1)), + Parameter(:R₀, "R₀", "the initial recovered population", Unit("persons", nomath), 0.0, Uniform(0, 4)), + ], + Time(:t, Unit("day", nomath))) + +odelist = ODEList([ + Time(:t, Unit("day", nomath)), + Parameter(:β, "β", "the beta parameter", Unit("1/(persons^2*day)", nomath), 1e-2, Uniform(1e-3, 2e-2)), + Rate(:inf, ExpressionFormula("S*I*β", nomath)), + + Parameter(:γ, "γ", "the gama parameter", Unit("1/(persons*day)", nomath), 3, Uniform(1, 2e+2)), + Rate(:rec, ExpressionFormula("I*γ", nomath)), + + Parameter(:S₀, "S₀", "the initial susceptible population", Unit("persons", nomath), 300000000.0, Uniform(1e6, 4e6)), + Initial(:S₀, ExpressionFormula("S₀", nomath)), + + Parameter(:I₀, "I₀", "the initial infected population", Unit("persons", nomath), 1.0, Uniform(1, 1)), + Initial(:I₀, ExpressionFormula("I₀", nomath)), + + Parameter(:R₀, "R₀", "the initial recovered population", Unit("persons", nomath), 0.0, Uniform(0, 4)), + Initial(:R₀, ExpressionFormula("R₀", nomath)), + + ]) + +amr₁ = ASKEModel(header, + model, + [ode] +) + +typesystem = acsetspec(:(LabelledPetriNet{Symbol}), quote + 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) + + +typing = Typing( + typesystem, + [ + (:S=>:Pop), + (:I=>:Pop), + (:R=>:Pop), + (:inf=>:inf), + (:rec=>:disease) + ] +) + +amr₂ = ASKEModel(header, + model, + [ + odelist, + typing + ] +) + +println() +println(amr_to_string(amr₁)) +println() +println(amr_to_string(amr₂)) + +AMR.amr_to_expr(amr₁) |> println +AMR.amr_to_expr(amr₂.header) |> println +AMR.amr_to_expr(amr₂.model) |> println +map(AMR.amr_to_expr(amr₂.semantics[1]).args[2].args) do s; println(s) end +AMR.amr_to_expr(amr₂.semantics[1]).args[2] +AMR.amr_to_expr(amr₂.semantics[1]) +AMR.amr_to_expr(amr₂) |> println + +h = AMR.load(Header, Dict("name" => "SIR Model", +"schema" => "https://raw.githubusercontent.com/DARPA-ASKEM/Model-Representations/petrinet_v0.5/petrinet/petrinet_schema.json", +"description" => "SIR model", +"schema_name" => "petrinet", +"model_version" => "0.1")) + +@test AMR.amr_to_string(h) == "\"\"\"\nASKE Model Representation: SIR Model0.1 :: petrinet \n https://raw.githubusercontent.com/DARPA-ASKEM/Model-Representations/petrinet_v0.5/petrinet/petrinet_schema.json\n\nSIR model\n\"\"\"" + +mjson = raw"""{ + "states": [ + { + "id": "S", + "name": "Susceptible", + "description": "Number of individuals that are 'susceptible' to a disease infection", + "grounding": { + "identifiers": { + "ido": "0000514" + } + }, + "units": { + "expression": "person", + "expression_mathml": "person" + } + }, + { + "id": "I", + "name": "Infected", + "description": "Number of individuals that are 'infected' by a disease", + "grounding": { + "identifiers": { + "ido": "0000511" + } + }, + "units": { + "expression": "person", + "expression_mathml": "person" + } + }, + { + "id": "R", + "name": "Recovered", + "description": "Number of individuals that have 'recovered' from a disease infection", + "grounding": { + "identifiers": { + "ido": "0000592" + } + }, + "units": { + "expression": "person", + "expression_mathml": "person" + } + } + ], + "transitions": [ + { + "id": "inf", + "input": [ + "S", + "I" + ], + "output": [ + "I", + "I" + ], + "properties": { + "name": "Infection", + "description": "Infective process between individuals" + } + }, + { + "id": "rec", + "input": [ + "I" + ], + "output": [ + "R" + ], + "properties": { + "name": "Recovery", + "description": "Recovery process of a infected individual" + } + } + ] + } + """ +using JSON +modeldict = JSON.parse(mjson) +using ACSets.ADTs + +AMR.petrispec(modeldict) |> ACSets.ADTs.to_string |> println + +semantics_str = raw"""{ + "ode": { + "rates": [ + { + "target": "inf", + "expression": "S*I*beta", + "expression_mathml": "SIbeta" + }, + { + "target": "rec", + "expression": "I*gamma", + "expression_mathml": "Igamma" + } + ], + "initials": [ + { + "target": "S", + "expression": "S0", + "expression_mathml": "S0" + }, + { + "target": "I", + "expression": "I0", + "expression_mathml": "I0" + }, + { + "target": "R", + "expression": "R0", + "expression_mathml": "R0" + } + ], + "parameters": [ + { + "id": "beta", + "name": "β", + "description": "infection rate", + "units": { + "expression": "1/(person*day)", + "expression_mathml": "1personday" + }, + "value": 2.7e-7, + "distribution": { + "type": "StandardUniform1", + "parameters": { + "minimum": 2.6e-7, + "maximum": 2.8e-7 + } + } + }, + { + "id": "gamma", + "name": "γ", + "description": "recovery rate", + "grounding": { + "identifiers": { + "askemo": "0000013" + } + }, + "units": { + "expression": "1/day", + "expression_mathml": "1day" + }, + "value": 0.14, + "distribution": { + "type": "StandardUniform1", + "parameters": { + "minimum": 0.1, + "maximum": 0.18 + } + } + }, + { + "id": "S0", + "name": "S₀", + "description": "Total susceptible population at timestep 0", + "value": 1000 + }, + { + "id": "I0", + "name": "I₀", + "description": "Total infected population at timestep 0", + "value": 1 + }, + { + "id": "R0", + "name": "R₀", + "description": "Total recovered population at timestep 0", + "value": 0 + } + ], + "observables": [ + { + "id": "noninf", + "name": "Non-infectious", + "states": [ + "S", + "R" + ], + "expression": "S+R", + "expression_mathml": "SR" + } + ], + "time": { + "id": "t", + "units": { + "expression": "day", + "expression_mathml": "day" + } + } + } +} +""" +semantics_dict = JSON.parse(semantics_str) +@show semantics_dict + +AMR.load(ODERecord, semantics_dict["ode"]) |> AMR.amr_to_string |> println + +sirmodel_dict = JSON.parsefile(joinpath([@__DIR__, "..", "..", "petrinet", "examples", "sir.json"])) +AMR.optload(AMRExamples.sirmodel_dict, ["model", :states], nothing) |> show +sirmodel = AMR.load(ASKEModel, sirmodel_dict) +sirmodel |> AMR.amr_to_string |> println + +sirmodel_dict = JSON.parsefile(joinpath([@__DIR__, "..", "..", "petrinet", "examples", "sir_typed.json"])) +semtyp = sirmodel_dict["semantics"]["typing"] + +sirmodel = AMR.load(Typing, semtyp) |> AMR.amr_to_string |> println + +sirmodel = AMR.load(ASKEModel, sirmodel_dict) +sirmodel |> AMR.amr_to_string |> println + +# Deserializing from Human readable strings + + +@testset "Loading Time" begin + @test AMR.load(AMR.Time, Base.Meta.parse("t::Time{}")) == AMR.Time(:t, AMR.Unit("", nomath)) + @test AMR.load(AMR.Time, Base.Meta.parse("t::Time{day}")) == AMR.Time(:t, AMR.Unit("day", nomath)) + @test AMR.load(AMR.Time, Base.Meta.parse("t::Time{day^2}")) == AMR.Time(:t, AMR.Unit("day ^ 2", nomath)) + @test_throws ErrorException AMR.load(AMR.Time, Base.Meta.parse("t::time{}")) + @test_throws ErrorException AMR.load(AMR.Time, Base.Meta.parse("t::time")) +end + +@testset "Loading Rates" begin + infspec = Meta.parse("inf::Rate = S*I*beta") + @test AMR.load(AMR.Rate, infspec).target == :inf + @test AMR.load(AMR.Rate, infspec).f.expression == "S * I * beta" + infspec = Meta.parse("inf::Rate{persons/time} = S*I*beta") + @test AMR.load(AMR.Rate, infspec).target == :inf + @test AMR.load(AMR.Rate, infspec).f.expression == "S * I * beta" +end +@testset "Loading Initials" begin + infspec = Meta.parse("S0::Initial = S*I*beta") + @test AMR.load(AMR.Initial, infspec).target == :S0 + @test AMR.load(AMR.Initial, infspec).f.expression == "S * I * beta" + infspec = Meta.parse("S0::Initial{persons/time} = S*I*beta") + @test AMR.load(AMR.Initial, infspec).target == :S0 + @test AMR.load(AMR.Initial, infspec).f.expression == "S * I * beta" +end + +@testset "Loading Parameters" begin + @testset "No Units" begin + paramstr = raw""" + \"\"\" + R₀ -- Total recovered population at timestep 0 + \"\"\" + R0::Parameter{} = 0.0 ~ δ(missing) + """ + paramexp = Meta.parse(paramstr) + param = AMR.load(Parameter, paramexp) + @test param.units == AMR.nounit + @test param.id == :R0 + end + @testset "Units" begin + + paramstr = raw""" + \"\"\" + R₀ -- Total recovered population at timestep 0 + \"\"\" + R0::Parameter{persons/day} = 0.0 ~ δ(missing) + """ + paramexp = Meta.parse(paramstr) + param = AMR.load(Parameter, paramexp) + @test param.id == :R0 + @test param.units == Unit("persons / day", AMR.nomath) + @test param.distribution == PointMass(:missing) + + paramstr = raw""" + \"\"\" + R₀ -- Total recovered population at timestep 0 + \"\"\" + R0::Parameter{persons/day} = 0.0 ~ δ(0.0) + """ + paramexp = Meta.parse(paramstr) + param = AMR.load(Parameter, paramexp) + @test param.id == :R0 + @test param.units == Unit("persons / day", AMR.nomath) + @test param.distribution == PointMass(0.0) + end +end + +odelist_expr = Meta.parse(raw""" +ODE_Record = begin + +inf::Rate = S*I*beta +rec::Rate = I*gamma +S::Initial = S0 +I::Initial = I0 +R::Initial = R0 + +\"\"\" β -- 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 +""") + +ol = AMR.load(ODEList, odelist_expr) +AMR.amr_to_string(ol) |> println + + +header_expr = Meta.parse(raw""" +\"\"\" +ASKE Model Representation: SIR Model@v0.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 +\"\"\" +""") + +AMR.load(Header, header_expr) + +modelrep = raw""" +begin +\"\"\" +ASKE Model Representation: SIR Model@0.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_Record = begin + +inf::Rate = S*I*beta +rec::Rate = I*gamma +S::Initial = S0 +I::Initial = I0 +R::Initial = R0 + +\"\"\" β -- 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 +end +""" +println(modelrep) +model_expr = Base.Meta.parse(modelrep) +@test AMR.load(ASKEModel, model_expr).header isa Header +@test AMR.load(ASKEModel, model_expr).model isa ACSetSpec +@test length(AMR.load(ODEList, model_expr.args[4]).statements) == 8 +@test length(AMR.load(ASKEModel, model_expr).semantics[1].statements) == 8 +@test AMR.load(Typing, model_expr.args[6]).system isa ACSetSpec +@test AMR.load(Typing, model_expr.args[6]).map isa Vector{Pair} +println(AMR.amr_to_string(AMR.load(ASKEModel, model_expr))) +@test_skip (AMR.amr_to_string(AMR.load(ASKEModel, model_expr))) == modelrep + +end